All Files (53.36% covered at 6.92 hits/line)
343 files in total.
17618 relevant lines.
9401 lines covered and
8217 lines missed
-
1
require 'active_support/test_case'
-
1
require 'rails-dom-testing'
-
-
1
module ActionMailer
-
1
class NonInferrableMailerError < ::StandardError
-
1
def initialize(name)
-
super "Unable to determine the mailer to test from #{name}. " +
-
"You'll need to specify it using tests YourMailer in your " +
-
"test case definition"
-
end
-
end
-
-
1
class TestCase < ActiveSupport::TestCase
-
1
module Behavior
-
1
extend ActiveSupport::Concern
-
-
1
include ActiveSupport::Testing::ConstantLookup
-
1
include TestHelper
-
1
include Rails::Dom::Testing::Assertions::SelectorAssertions
-
1
include Rails::Dom::Testing::Assertions::DomAssertions
-
-
1
included do
-
1
class_attribute :_mailer_class
-
1
setup :initialize_test_deliveries
-
1
setup :set_expected_mail
-
1
teardown :restore_test_deliveries
-
end
-
-
1
module ClassMethods
-
1
def tests(mailer)
-
case mailer
-
when String, Symbol
-
self._mailer_class = mailer.to_s.camelize.constantize
-
when Module
-
self._mailer_class = mailer
-
else
-
raise NonInferrableMailerError.new(mailer)
-
end
-
end
-
-
1
def mailer_class
-
if mailer = self._mailer_class
-
mailer
-
else
-
tests determine_default_mailer(name)
-
end
-
end
-
-
1
def determine_default_mailer(name)
-
mailer = determine_constant_from_test_name(name) do |constant|
-
Class === constant && constant < ActionMailer::Base
-
end
-
raise NonInferrableMailerError.new(name) if mailer.nil?
-
mailer
-
end
-
end
-
-
1
protected
-
-
1
def initialize_test_deliveries
-
set_delivery_method :test
-
@old_perform_deliveries = ActionMailer::Base.perform_deliveries
-
ActionMailer::Base.perform_deliveries = true
-
end
-
-
1
def restore_test_deliveries
-
restore_delivery_method
-
ActionMailer::Base.perform_deliveries = @old_perform_deliveries
-
ActionMailer::Base.deliveries.clear
-
end
-
-
1
def set_delivery_method(method)
-
@old_delivery_method = ActionMailer::Base.delivery_method
-
ActionMailer::Base.delivery_method = method
-
end
-
-
1
def restore_delivery_method
-
ActionMailer::Base.delivery_method = @old_delivery_method
-
end
-
-
1
def set_expected_mail
-
@expected = Mail.new
-
@expected.content_type ["text", "plain", { "charset" => charset }]
-
@expected.mime_version = '1.0'
-
end
-
-
1
private
-
-
1
def charset
-
"UTF-8"
-
end
-
-
1
def encode(subject)
-
Mail::Encodings.q_value_encode(subject, charset)
-
end
-
-
1
def read_fixture(action)
-
IO.readlines(File.join(Rails.root, 'test', 'fixtures', self.class.mailer_class.name.underscore, action))
-
end
-
end
-
-
1
include Behavior
-
end
-
end
-
1
module ActionMailer
-
# Provides helper methods for testing Action Mailer, including #assert_emails
-
# and #assert_no_emails
-
1
module TestHelper
-
# Asserts that the number of emails sent matches the given number.
-
#
-
# def test_emails
-
# assert_emails 0
-
# ContactMailer.welcome.deliver_now
-
# assert_emails 1
-
# ContactMailer.welcome.deliver_now
-
# assert_emails 2
-
# end
-
#
-
# If a block is passed, that block should cause the specified number of
-
# emails to be sent.
-
#
-
# def test_emails_again
-
# assert_emails 1 do
-
# ContactMailer.welcome.deliver_now
-
# end
-
#
-
# assert_emails 2 do
-
# ContactMailer.welcome.deliver_now
-
# ContactMailer.welcome.deliver_now
-
# end
-
# end
-
1
def assert_emails(number)
-
if block_given?
-
original_count = ActionMailer::Base.deliveries.size
-
yield
-
new_count = ActionMailer::Base.deliveries.size
-
assert_equal number, new_count - original_count, "#{number} emails expected, but #{new_count - original_count} were sent"
-
else
-
assert_equal number, ActionMailer::Base.deliveries.size
-
end
-
end
-
-
# Assert that no emails have been sent.
-
#
-
# def test_emails
-
# assert_no_emails
-
# ContactMailer.welcome.deliver_now
-
# assert_emails 1
-
# end
-
#
-
# If a block is passed, that block should not cause any emails to be sent.
-
#
-
# def test_emails_again
-
# assert_no_emails do
-
# # No emails should be sent from this block
-
# end
-
# end
-
#
-
# Note: This assertion is simply a shortcut for:
-
#
-
# assert_emails 0
-
1
def assert_no_emails(&block)
-
assert_emails 0, &block
-
end
-
end
-
end
-
1
require 'action_view'
-
1
require "action_controller/log_subscriber"
-
1
require "action_controller/metal/params_wrapper"
-
-
1
module ActionController
-
# Action Controllers are the core of a web request in \Rails. They are made up of one or more actions that are executed
-
# on request and then either it renders a template or redirects to another action. An action is defined as a public method
-
# on the controller, which will automatically be made accessible to the web-server through \Rails Routes.
-
#
-
# By default, only the ApplicationController in a \Rails application inherits from <tt>ActionController::Base</tt>. All other
-
# controllers in turn inherit from ApplicationController. This gives you one class to configure things such as
-
# request forgery protection and filtering of sensitive request parameters.
-
#
-
# A sample controller could look like this:
-
#
-
# class PostsController < ApplicationController
-
# def index
-
# @posts = Post.all
-
# end
-
#
-
# def create
-
# @post = Post.create params[:post]
-
# redirect_to posts_path
-
# end
-
# end
-
#
-
# Actions, by default, render a template in the <tt>app/views</tt> directory corresponding to the name of the controller and action
-
# after executing code in the action. For example, the +index+ action of the PostsController would render the
-
# template <tt>app/views/posts/index.html.erb</tt> by default after populating the <tt>@posts</tt> instance variable.
-
#
-
# Unlike index, the create action will not render a template. After performing its main purpose (creating a
-
# new post), it initiates a redirect instead. This redirect works by returning an external
-
# "302 Moved" HTTP response that takes the user to the index action.
-
#
-
# These two methods represent the two basic action archetypes used in Action Controllers. Get-and-show and do-and-redirect.
-
# Most actions are variations on these themes.
-
#
-
# == Requests
-
#
-
# For every request, the router determines the value of the +controller+ and +action+ keys. These determine which controller
-
# and action are called. The remaining request parameters, the session (if one is available), and the full request with
-
# all the HTTP headers are made available to the action through accessor methods. Then the action is performed.
-
#
-
# The full request object is available via the request accessor and is primarily used to query for HTTP headers:
-
#
-
# def server_ip
-
# location = request.env["REMOTE_ADDR"]
-
# render plain: "This server hosted at #{location}"
-
# end
-
#
-
# == Parameters
-
#
-
# All request parameters, whether they come from a GET or POST request, or from the URL, are available through the params method
-
# which returns a hash. For example, an action that was performed through <tt>/posts?category=All&limit=5</tt> will include
-
# <tt>{ "category" => "All", "limit" => "5" }</tt> in params.
-
#
-
# It's also possible to construct multi-dimensional parameter hashes by specifying keys using brackets, such as:
-
#
-
# <input type="text" name="post[name]" value="david">
-
# <input type="text" name="post[address]" value="hyacintvej">
-
#
-
# A request stemming from a form holding these inputs will include <tt>{ "post" => { "name" => "david", "address" => "hyacintvej" } }</tt>.
-
# If the address input had been named <tt>post[address][street]</tt>, the params would have included
-
# <tt>{ "post" => { "address" => { "street" => "hyacintvej" } } }</tt>. There's no limit to the depth of the nesting.
-
#
-
# == Sessions
-
#
-
# Sessions allow you to store objects in between requests. This is useful for objects that are not yet ready to be persisted,
-
# such as a Signup object constructed in a multi-paged process, or objects that don't change much and are needed all the time, such
-
# as a User object for a system that requires login. The session should not be used, however, as a cache for objects where it's likely
-
# they could be changed unknowingly. It's usually too much work to keep it all synchronized -- something databases already excel at.
-
#
-
# You can place objects in the session by using the <tt>session</tt> method, which accesses a hash:
-
#
-
# session[:person] = Person.authenticate(user_name, password)
-
#
-
# And retrieved again through the same hash:
-
#
-
# Hello #{session[:person]}
-
#
-
# For removing objects from the session, you can either assign a single key to +nil+:
-
#
-
# # removes :person from session
-
# session[:person] = nil
-
#
-
# or you can remove the entire session with +reset_session+.
-
#
-
# Sessions are stored by default in a browser cookie that's cryptographically signed, but unencrypted.
-
# This prevents the user from tampering with the session but also allows them to see its contents.
-
#
-
# Do not put secret information in cookie-based sessions!
-
#
-
# == Responses
-
#
-
# Each action results in a response, which holds the headers and document to be sent to the user's browser. The actual response
-
# object is generated automatically through the use of renders and redirects and requires no user intervention.
-
#
-
# == Renders
-
#
-
# Action Controller sends content to the user by using one of five rendering methods. The most versatile and common is the rendering
-
# of a template. Included in the Action Pack is the Action View, which enables rendering of ERB templates. It's automatically configured.
-
# The controller passes objects to the view by assigning instance variables:
-
#
-
# def show
-
# @post = Post.find(params[:id])
-
# end
-
#
-
# Which are then automatically available to the view:
-
#
-
# Title: <%= @post.title %>
-
#
-
# You don't have to rely on the automated rendering. For example, actions that could result in the rendering of different templates
-
# will use the manual rendering methods:
-
#
-
# def search
-
# @results = Search.find(params[:query])
-
# case @results.count
-
# when 0 then render action: "no_results"
-
# when 1 then render action: "show"
-
# when 2..10 then render action: "show_many"
-
# end
-
# end
-
#
-
# Read more about writing ERB and Builder templates in ActionView::Base.
-
#
-
# == Redirects
-
#
-
# Redirects are used to move from one action to another. For example, after a <tt>create</tt> action, which stores a blog entry to the
-
# database, we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're
-
# going to reuse (and redirect to) a <tt>show</tt> action that we'll assume has already been created. The code might look like this:
-
#
-
# def create
-
# @entry = Entry.new(params[:entry])
-
# if @entry.save
-
# # The entry was saved correctly, redirect to show
-
# redirect_to action: 'show', id: @entry.id
-
# else
-
# # things didn't go so well, do something else
-
# end
-
# end
-
#
-
# In this case, after saving our new entry to the database, the user is redirected to the <tt>show</tt> method, which is then executed.
-
# Note that this is an external HTTP-level redirection which will cause the browser to make a second request (a GET to the show action),
-
# and not some internal re-routing which calls both "create" and then "show" within one request.
-
#
-
# Learn more about <tt>redirect_to</tt> and what options you have in ActionController::Redirecting.
-
#
-
# == Calling multiple redirects or renders
-
#
-
# An action may contain only a single render or a single redirect. Attempting to try to do either again will result in a DoubleRenderError:
-
#
-
# def do_something
-
# redirect_to action: "elsewhere"
-
# render action: "overthere" # raises DoubleRenderError
-
# end
-
#
-
# If you need to redirect on the condition of something, then be sure to add "and return" to halt execution.
-
#
-
# def do_something
-
# redirect_to(action: "elsewhere") and return if monkeys.nil?
-
# render action: "overthere" # won't be called if monkeys is nil
-
# end
-
#
-
1
class Base < Metal
-
1
abstract!
-
-
# We document the request and response methods here because albeit they are
-
# implemented in ActionController::Metal, the type of the returned objects
-
# is unknown at that level.
-
-
##
-
# :method: request
-
#
-
# Returns an ActionDispatch::Request instance that represents the
-
# current request.
-
-
##
-
# :method: response
-
#
-
# Returns an ActionDispatch::Response that represents the current
-
# response.
-
-
# Shortcut helper that returns all the modules included in
-
# ActionController::Base except the ones passed as arguments:
-
#
-
# class MyBaseController < ActionController::Metal
-
# ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left|
-
# include left
-
# end
-
# end
-
#
-
# This gives better control over what you want to exclude and makes it
-
# easier to create a bare controller class, instead of listing the modules
-
# required manually.
-
1
def self.without_modules(*modules)
-
modules = modules.map do |m|
-
m.is_a?(Symbol) ? ActionController.const_get(m) : m
-
end
-
-
MODULES - modules
-
end
-
-
1
MODULES = [
-
AbstractController::Rendering,
-
AbstractController::Translation,
-
AbstractController::AssetPaths,
-
-
Helpers,
-
HideActions,
-
UrlFor,
-
Redirecting,
-
ActionView::Layouts,
-
Rendering,
-
Renderers::All,
-
ConditionalGet,
-
EtagWithTemplateDigest,
-
RackDelegation,
-
Caching,
-
MimeResponds,
-
ImplicitRender,
-
StrongParameters,
-
-
Cookies,
-
Flash,
-
RequestForgeryProtection,
-
ForceSSL,
-
Streaming,
-
DataStreaming,
-
HttpAuthentication::Basic::ControllerMethods,
-
HttpAuthentication::Digest::ControllerMethods,
-
HttpAuthentication::Token::ControllerMethods,
-
-
# Before callbacks should also be executed the earliest as possible, so
-
# also include them at the bottom.
-
AbstractController::Callbacks,
-
-
# Append rescue at the bottom to wrap as much as possible.
-
Rescue,
-
-
# Add instrumentations hooks at the bottom, to ensure they instrument
-
# all the methods properly.
-
Instrumentation,
-
-
# Params wrapper should come before instrumentation so they are
-
# properly showed in logs
-
ParamsWrapper
-
]
-
-
1
MODULES.each do |mod|
-
30
include mod
-
end
-
-
# Define some internal variables that should not be propagated to the view.
-
1
PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + [
-
:@_status, :@_headers, :@_params, :@_env, :@_response, :@_request,
-
:@_view_runtime, :@_stream, :@_url_options, :@_action_has_layout ]
-
-
1
def _protected_ivars # :nodoc:
-
25
PROTECTED_IVARS
-
end
-
-
1
def self.protected_instance_variables
-
PROTECTED_IVARS
-
end
-
-
1
ActiveSupport.run_load_hooks(:action_controller, self)
-
end
-
end
-
1
require 'fileutils'
-
1
require 'uri'
-
1
require 'set'
-
-
1
module ActionController
-
# \Caching is a cheap way of speeding up slow applications by keeping the result of
-
# calculations, renderings, and database calls around for subsequent requests.
-
#
-
# You can read more about each approach by clicking the modules below.
-
#
-
# Note: To turn off all caching, set
-
# config.action_controller.perform_caching = false
-
#
-
# == \Caching stores
-
#
-
# All the caching stores from ActiveSupport::Cache are available to be used as backends
-
# for Action Controller caching.
-
#
-
# Configuration examples (FileStore is the default):
-
#
-
# config.action_controller.cache_store = :memory_store
-
# config.action_controller.cache_store = :file_store, '/path/to/cache/directory'
-
# config.action_controller.cache_store = :mem_cache_store, 'localhost'
-
# config.action_controller.cache_store = :mem_cache_store, Memcached::Rails.new('localhost:11211')
-
# config.action_controller.cache_store = MyOwnStore.new('parameter')
-
1
module Caching
-
1
extend ActiveSupport::Concern
-
1
extend ActiveSupport::Autoload
-
-
1
eager_autoload do
-
1
autoload :Fragments
-
end
-
-
1
module ConfigMethods
-
1
def cache_store
-
config.cache_store
-
end
-
-
1
def cache_store=(store)
-
1
config.cache_store = ActiveSupport::Cache.lookup_store(store)
-
end
-
-
1
private
-
1
def cache_configured?
-
perform_caching && cache_store
-
end
-
end
-
-
1
include RackDelegation
-
1
include AbstractController::Callbacks
-
-
1
include ConfigMethods
-
1
include Fragments
-
-
1
included do
-
1
extend ConfigMethods
-
-
1
config_accessor :default_static_extension
-
1
self.default_static_extension ||= '.html'
-
-
1
config_accessor :perform_caching
-
1
self.perform_caching = true if perform_caching.nil?
-
-
1
class_attribute :_view_cache_dependencies
-
1
self._view_cache_dependencies = []
-
1
helper_method :view_cache_dependencies if respond_to?(:helper_method)
-
end
-
-
1
module ClassMethods
-
1
def view_cache_dependency(&dependency)
-
self._view_cache_dependencies += [dependency]
-
end
-
end
-
-
1
def view_cache_dependencies
-
self.class._view_cache_dependencies.map { |dep| instance_exec(&dep) }.compact
-
end
-
-
1
protected
-
# Convenience accessor.
-
1
def cache(key, options = {}, &block)
-
if cache_configured?
-
cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block)
-
else
-
yield
-
end
-
end
-
end
-
end
-
1
module ActionController
-
1
module Caching
-
# Fragment caching is used for caching various blocks within
-
# views without caching the entire action as a whole. This is
-
# useful when certain elements of an action change frequently or
-
# depend on complicated state while other parts rarely change or
-
# can be shared amongst multiple parties. The caching is done using
-
# the +cache+ helper available in the Action View. See
-
# ActionView::Helpers::CacheHelper for more information.
-
#
-
# While it's strongly recommended that you use key-based cache
-
# expiration (see links in CacheHelper for more information),
-
# it is also possible to manually expire caches. For example:
-
#
-
# expire_fragment('name_of_cache')
-
1
module Fragments
-
# Given a key (as described in +expire_fragment+), returns
-
# a key suitable for use in reading, writing, or expiring a
-
# cached fragment. All keys are prefixed with <tt>views/</tt> and uses
-
# ActiveSupport::Cache.expand_cache_key for the expansion.
-
1
def fragment_cache_key(key)
-
ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views)
-
end
-
-
# Writes +content+ to the location signified by
-
# +key+ (see +expire_fragment+ for acceptable formats).
-
1
def write_fragment(key, content, options = nil)
-
return content unless cache_configured?
-
-
key = fragment_cache_key(key)
-
instrument_fragment_cache :write_fragment, key do
-
content = content.to_str
-
cache_store.write(key, content, options)
-
end
-
content
-
end
-
-
# Reads a cached fragment from the location signified by +key+
-
# (see +expire_fragment+ for acceptable formats).
-
1
def read_fragment(key, options = nil)
-
return unless cache_configured?
-
-
key = fragment_cache_key(key)
-
instrument_fragment_cache :read_fragment, key do
-
result = cache_store.read(key, options)
-
result.respond_to?(:html_safe) ? result.html_safe : result
-
end
-
end
-
-
# Check if a cached fragment from the location signified by
-
# +key+ exists (see +expire_fragment+ for acceptable formats).
-
1
def fragment_exist?(key, options = nil)
-
return unless cache_configured?
-
key = fragment_cache_key(key)
-
-
instrument_fragment_cache :exist_fragment?, key do
-
cache_store.exist?(key, options)
-
end
-
end
-
-
# Removes fragments from the cache.
-
#
-
# +key+ can take one of three forms:
-
#
-
# * String - This would normally take the form of a path, like
-
# <tt>pages/45/notes</tt>.
-
# * Hash - Treated as an implicit call to +url_for+, like
-
# <tt>{ controller: 'pages', action: 'notes', id: 45}</tt>
-
# * Regexp - Will remove any fragment that matches, so
-
# <tt>%r{pages/\d*/notes}</tt> might remove all notes. Make sure you
-
# don't use anchors in the regex (<tt>^</tt> or <tt>$</tt>) because
-
# the actual filename matched looks like
-
# <tt>./cache/filename/path.cache</tt>. Note: Regexp expiration is
-
# only supported on caches that can iterate over all keys (unlike
-
# memcached).
-
#
-
# +options+ is passed through to the cache store's +delete+
-
# method (or <tt>delete_matched</tt>, for Regexp keys).
-
1
def expire_fragment(key, options = nil)
-
return unless cache_configured?
-
key = fragment_cache_key(key) unless key.is_a?(Regexp)
-
-
instrument_fragment_cache :expire_fragment, key do
-
if key.is_a?(Regexp)
-
cache_store.delete_matched(key, options)
-
else
-
cache_store.delete(key, options)
-
end
-
end
-
end
-
-
1
def instrument_fragment_cache(name, key) # :nodoc:
-
payload = {
-
controller: controller_name,
-
action: action_name,
-
key: key
-
}
-
-
ActiveSupport::Notifications.instrument("#{name}.action_controller", payload) { yield }
-
end
-
end
-
end
-
end
-
1
module ActionController
-
1
class LogSubscriber < ActiveSupport::LogSubscriber
-
1
INTERNAL_PARAMS = %w(controller action format _method only_path)
-
-
1
def start_processing(event)
-
16
return unless logger.info?
-
-
16
payload = event.payload
-
16
params = payload[:params].except(*INTERNAL_PARAMS)
-
16
format = payload[:format]
-
16
format = format.to_s.upcase if format.is_a?(Symbol)
-
-
16
info "Processing by #{payload[:controller]}##{payload[:action]} as #{format}"
-
16
info " Parameters: #{params.inspect}" unless params.empty?
-
end
-
-
1
def process_action(event)
-
16
info do
-
16
payload = event.payload
-
16
additions = ActionController::Base.log_process_action(payload)
-
-
16
status = payload[:status]
-
16
if status.nil? && payload[:exception].present?
-
exception_class_name = payload[:exception].first
-
status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
-
end
-
16
message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms"
-
16
message << " (#{additions.join(" | ")})" unless additions.blank?
-
16
message
-
end
-
end
-
-
1
def halted_callback(event)
-
info { "Filter chain halted as #{event.payload[:filter].inspect} rendered or redirected" }
-
end
-
-
1
def send_file(event)
-
info { "Sent file #{event.payload[:path]} (#{event.duration.round(1)}ms)" }
-
end
-
-
1
def redirect_to(event)
-
14
info { "Redirected to #{event.payload[:location]}" }
-
end
-
-
1
def send_data(event)
-
info { "Sent data #{event.payload[:filename]} (#{event.duration.round(1)}ms)" }
-
end
-
-
1
def unpermitted_parameters(event)
-
1
debug do
-
1
unpermitted_keys = event.payload[:keys]
-
1
"Unpermitted parameter#{'s' if unpermitted_keys.size > 1}: #{unpermitted_keys.join(", ")}"
-
end
-
end
-
-
1
def deep_munge(event)
-
debug do
-
"Value for params[:#{event.payload[:keys].join('][:')}] was set "\
-
"to nil, because it was one of [], [null] or [null, null, ...]. "\
-
"Go to http://guides.rubyonrails.org/security.html#unsafe-query-generation "\
-
"for more information."\
-
end
-
end
-
-
%w(write_fragment read_fragment exist_fragment?
-
1
expire_fragment expire_page write_page).each do |method|
-
6
class_eval <<-METHOD, __FILE__, __LINE__ + 1
-
def #{method}(event)
-
return unless logger.info?
-
key_or_path = event.payload[:key] || event.payload[:path]
-
human_name = #{method.to_s.humanize.inspect}
-
info("\#{human_name} \#{key_or_path} (\#{event.duration.round(1)}ms)")
-
end
-
METHOD
-
end
-
-
1
def logger
-
206
ActionController::Base.logger
-
end
-
end
-
end
-
-
1
ActionController::LogSubscriber.attach_to :action_controller
-
1
require 'active_support/core_ext/array/extract_options'
-
1
require 'action_dispatch/middleware/stack'
-
-
1
module ActionController
-
# Extend ActionDispatch middleware stack to make it aware of options
-
# allowing the following syntax in controllers:
-
#
-
# class PostsController < ApplicationController
-
# use AuthenticationMiddleware, except: [:index, :show]
-
# end
-
#
-
1
class MiddlewareStack < ActionDispatch::MiddlewareStack #:nodoc:
-
1
class Middleware < ActionDispatch::MiddlewareStack::Middleware #:nodoc:
-
1
def initialize(klass, *args, &block)
-
options = args.extract_options!
-
@only = Array(options.delete(:only)).map(&:to_s)
-
@except = Array(options.delete(:except)).map(&:to_s)
-
args << options unless options.empty?
-
super
-
end
-
-
1
def valid?(action)
-
if @only.present?
-
@only.include?(action)
-
elsif @except.present?
-
!@except.include?(action)
-
else
-
true
-
end
-
end
-
end
-
-
1
def build(action, app = Proc.new)
-
action = action.to_s
-
-
middlewares.reverse.inject(app) do |a, middleware|
-
middleware.valid?(action) ? middleware.build(a) : a
-
end
-
end
-
end
-
-
# <tt>ActionController::Metal</tt> is the simplest possible controller, providing a
-
# valid Rack interface without the additional niceties provided by
-
# <tt>ActionController::Base</tt>.
-
#
-
# A sample metal controller might look like this:
-
#
-
# class HelloController < ActionController::Metal
-
# def index
-
# self.response_body = "Hello World!"
-
# end
-
# end
-
#
-
# And then to route requests to your metal controller, you would add
-
# something like this to <tt>config/routes.rb</tt>:
-
#
-
# get 'hello', to: HelloController.action(:index)
-
#
-
# The +action+ method returns a valid Rack application for the \Rails
-
# router to dispatch to.
-
#
-
# == Rendering Helpers
-
#
-
# <tt>ActionController::Metal</tt> by default provides no utilities for rendering
-
# views, partials, or other responses aside from explicitly calling of
-
# <tt>response_body=</tt>, <tt>content_type=</tt>, and <tt>status=</tt>. To
-
# add the render helpers you're used to having in a normal controller, you
-
# can do the following:
-
#
-
# class HelloController < ActionController::Metal
-
# include AbstractController::Rendering
-
# include ActionView::Layouts
-
# append_view_path "#{Rails.root}/app/views"
-
#
-
# def index
-
# render "hello/index"
-
# end
-
# end
-
#
-
# == Redirection Helpers
-
#
-
# To add redirection helpers to your metal controller, do the following:
-
#
-
# class HelloController < ActionController::Metal
-
# include ActionController::Redirecting
-
# include Rails.application.routes.url_helpers
-
#
-
# def index
-
# redirect_to root_url
-
# end
-
# end
-
#
-
# == Other Helpers
-
#
-
# You can refer to the modules included in <tt>ActionController::Base</tt> to see
-
# other features you can bring into your metal controller.
-
#
-
1
class Metal < AbstractController::Base
-
1
abstract!
-
-
1
attr_internal_writer :env
-
-
1
def env
-
84
@_env ||= {}
-
end
-
-
# Returns the last part of the controller's name, underscored, without the ending
-
# <tt>Controller</tt>. For instance, PostsController returns <tt>posts</tt>.
-
# Namespaces are left out, so Admin::PostsController returns <tt>posts</tt> as well.
-
#
-
# ==== Returns
-
# * <tt>string</tt>
-
1
def self.controller_name
-
@controller_name ||= name.demodulize.sub(/Controller$/, '').underscore
-
end
-
-
# Delegates to the class' <tt>controller_name</tt>
-
1
def controller_name
-
self.class.controller_name
-
end
-
-
# The details below can be overridden to support a specific
-
# Request and Response object. The default ActionController::Base
-
# implementation includes RackDelegation, which makes a request
-
# and response object available. You might wish to control the
-
# environment and response manually for performance reasons.
-
-
1
attr_internal :headers, :response, :request
-
1
delegate :session, :to => "@_request"
-
-
1
def initialize
-
18
@_headers = {"Content-Type" => "text/html"}
-
18
@_status = 200
-
18
@_request = nil
-
18
@_response = nil
-
18
@_routes = nil
-
18
super
-
end
-
-
1
def params
-
@_params ||= request.parameters
-
end
-
-
1
def params=(val)
-
@_params = val
-
end
-
-
# Basic implementations for content_type=, location=, and headers are
-
# provided to reduce the dependency on the RackDelegation module
-
# in Renderer and Redirector.
-
-
1
def content_type=(type)
-
headers["Content-Type"] = type.to_s
-
end
-
-
1
def content_type
-
headers["Content-Type"]
-
end
-
-
1
def location
-
headers["Location"]
-
end
-
-
1
def location=(url)
-
headers["Location"] = url
-
end
-
-
# Basic url_for that can be overridden for more robust functionality
-
1
def url_for(string)
-
string
-
end
-
-
1
def status
-
@_status
-
end
-
1
alias :response_code :status # :nodoc:
-
-
1
def status=(status)
-
@_status = Rack::Utils.status_code(status)
-
end
-
-
1
def response_body=(body)
-
16
body = [body] unless body.nil? || body.respond_to?(:each)
-
16
super
-
end
-
-
# Tests if render or redirect has already happened.
-
1
def performed?
-
16
response_body || (response && response.committed?)
-
end
-
-
1
def dispatch(name, request) #:nodoc:
-
@_request = request
-
@_env = request.env
-
@_env['action_controller.instance'] = self
-
process(name)
-
to_a
-
end
-
-
1
def to_a #:nodoc:
-
response ? response.to_a : [status, headers, response_body]
-
end
-
-
1
class_attribute :middleware_stack
-
1
self.middleware_stack = ActionController::MiddlewareStack.new
-
-
1
def self.inherited(base) # :nodoc:
-
6
base.middleware_stack = middleware_stack.dup
-
6
super
-
end
-
-
# Pushes the given Rack middleware and its arguments to the bottom of the
-
# middleware stack.
-
1
def self.use(*args, &block)
-
middleware_stack.use(*args, &block)
-
end
-
-
# Alias for +middleware_stack+.
-
1
def self.middleware
-
middleware_stack
-
end
-
-
# Makes the controller a Rack endpoint that runs the action in the given
-
# +env+'s +action_dispatch.request.path_parameters+ key.
-
1
def self.call(env)
-
req = ActionDispatch::Request.new env
-
action(req.path_parameters[:action]).call(env)
-
end
-
-
# Returns a Rack endpoint for the given action name.
-
1
def self.action(name, klass = ActionDispatch::Request)
-
if middleware_stack.any?
-
middleware_stack.build(name) do |env|
-
new.dispatch(name, klass.new(env))
-
end
-
else
-
lambda { |env| new.dispatch(name, klass.new(env)) }
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/hash/keys'
-
-
1
module ActionController
-
1
module ConditionalGet
-
1
extend ActiveSupport::Concern
-
-
1
include RackDelegation
-
1
include Head
-
-
1
included do
-
1
class_attribute :etaggers
-
1
self.etaggers = []
-
end
-
-
1
module ClassMethods
-
# Allows you to consider additional controller-wide information when generating an ETag.
-
# For example, if you serve pages tailored depending on who's logged in at the moment, you
-
# may want to add the current user id to be part of the ETag to prevent authorized displaying
-
# of cached pages.
-
#
-
# class InvoicesController < ApplicationController
-
# etag { current_user.try :id }
-
#
-
# def show
-
# # Etag will differ even for the same invoice when it's viewed by a different current_user
-
# @invoice = Invoice.find(params[:id])
-
# fresh_when(@invoice)
-
# end
-
# end
-
1
def etag(&etagger)
-
1
self.etaggers += [etagger]
-
end
-
end
-
-
# Sets the +etag+, +last_modified+, or both on the response and renders a
-
# <tt>304 Not Modified</tt> response if the request is already fresh.
-
#
-
# === Parameters:
-
#
-
# * <tt>:etag</tt>.
-
# * <tt>:last_modified</tt>.
-
# * <tt>:public</tt> By default the Cache-Control header is private, set this to
-
# +true+ if you want your application to be cachable by other devices (proxy caches).
-
# * <tt>:template</tt> By default, the template digest for the current
-
# controller/action is included in ETags. If the action renders a
-
# different template, you can include its digest instead. If the action
-
# doesn't render a template at all, you can pass <tt>template: false</tt>
-
# to skip any attempt to check for a template digest.
-
#
-
# === Example:
-
#
-
# def show
-
# @article = Article.find(params[:id])
-
# fresh_when(etag: @article, last_modified: @article.created_at, public: true)
-
# end
-
#
-
# This will render the show template if the request isn't sending a matching ETag or
-
# If-Modified-Since header and just a <tt>304 Not Modified</tt> response if there's a match.
-
#
-
# You can also just pass a record where +last_modified+ will be set by calling
-
# +updated_at+ and the +etag+ by passing the object itself.
-
#
-
# def show
-
# @article = Article.find(params[:id])
-
# fresh_when(@article)
-
# end
-
#
-
# When passing a record, you can still set whether the public header:
-
#
-
# def show
-
# @article = Article.find(params[:id])
-
# fresh_when(@article, public: true)
-
# end
-
#
-
# When rendering a different template than the default controller/action
-
# style, you can indicate which digest to include in the ETag:
-
#
-
# before_action { fresh_when @article, template: 'widgets/show' }
-
#
-
1
def fresh_when(record_or_options, additional_options = {})
-
if record_or_options.is_a? Hash
-
options = record_or_options
-
options.assert_valid_keys(:etag, :last_modified, :public, :template)
-
else
-
record = record_or_options
-
options = { etag: record, last_modified: record.try(:updated_at) }.merge!(additional_options)
-
end
-
-
response.etag = combine_etags(options) if options[:etag] || options[:template]
-
response.last_modified = options[:last_modified] if options[:last_modified]
-
response.cache_control[:public] = true if options[:public]
-
-
head :not_modified if request.fresh?(response)
-
end
-
-
# Sets the +etag+ and/or +last_modified+ on the response and checks it against
-
# the client request. If the request doesn't match the options provided, the
-
# request is considered stale and should be generated from scratch. Otherwise,
-
# it's fresh and we don't need to generate anything and a reply of <tt>304 Not Modified</tt> is sent.
-
#
-
# === Parameters:
-
#
-
# * <tt>:etag</tt>.
-
# * <tt>:last_modified</tt>.
-
# * <tt>:public</tt> By default the Cache-Control header is private, set this to
-
# +true+ if you want your application to be cachable by other devices (proxy caches).
-
# * <tt>:template</tt> By default, the template digest for the current
-
# controller/action is included in ETags. If the action renders a
-
# different template, you can include its digest instead. If the action
-
# doesn't render a template at all, you can pass <tt>template: false</tt>
-
# to skip any attempt to check for a template digest.
-
#
-
# === Example:
-
#
-
# def show
-
# @article = Article.find(params[:id])
-
#
-
# if stale?(etag: @article, last_modified: @article.created_at)
-
# @statistics = @article.really_expensive_call
-
# respond_to do |format|
-
# # all the supported formats
-
# end
-
# end
-
# end
-
#
-
# You can also just pass a record where +last_modified+ will be set by calling
-
# +updated_at+ and the +etag+ by passing the object itself.
-
#
-
# def show
-
# @article = Article.find(params[:id])
-
#
-
# if stale?(@article)
-
# @statistics = @article.really_expensive_call
-
# respond_to do |format|
-
# # all the supported formats
-
# end
-
# end
-
# end
-
#
-
# When passing a record, you can still set whether the public header:
-
#
-
# def show
-
# @article = Article.find(params[:id])
-
#
-
# if stale?(@article, public: true)
-
# @statistics = @article.really_expensive_call
-
# respond_to do |format|
-
# # all the supported formats
-
# end
-
# end
-
# end
-
#
-
# When rendering a different template than the default controller/action
-
# style, you can indicate which digest to include in the ETag:
-
#
-
# def show
-
# super if stale? @article, template: 'widgets/show'
-
# end
-
#
-
1
def stale?(record_or_options, additional_options = {})
-
fresh_when(record_or_options, additional_options)
-
!request.fresh?(response)
-
end
-
-
# Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a +private+
-
# instruction, so that intermediate caches must not cache the response.
-
#
-
# expires_in 20.minutes
-
# expires_in 3.hours, public: true
-
# expires_in 3.hours, public: true, must_revalidate: true
-
#
-
# This method will overwrite an existing Cache-Control header.
-
# See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
-
#
-
# The method will also ensure a HTTP Date header for client compatibility.
-
1
def expires_in(seconds, options = {})
-
response.cache_control.merge!(
-
:max_age => seconds,
-
:public => options.delete(:public),
-
:must_revalidate => options.delete(:must_revalidate)
-
)
-
options.delete(:private)
-
-
response.cache_control[:extras] = options.map {|k,v| "#{k}=#{v}"}
-
response.date = Time.now unless response.date?
-
end
-
-
# Sets a HTTP 1.1 Cache-Control header of <tt>no-cache</tt> so no caching should
-
# occur by the browser or intermediate caches (like caching proxy servers).
-
1
def expires_now
-
response.cache_control.replace(:no_cache => true)
-
end
-
-
1
private
-
1
def combine_etags(options)
-
etags = etaggers.map { |etagger| instance_exec(options, &etagger) }.compact
-
etags.unshift options[:etag]
-
end
-
end
-
end
-
1
module ActionController #:nodoc:
-
1
module Cookies
-
1
extend ActiveSupport::Concern
-
-
1
include RackDelegation
-
-
1
included do
-
1
helper_method :cookies
-
end
-
-
1
private
-
1
def cookies
-
16
request.cookie_jar
-
end
-
end
-
end
-
1
require 'action_controller/metal/exceptions'
-
-
1
module ActionController #:nodoc:
-
# Methods for sending arbitrary data and for streaming files to the browser,
-
# instead of rendering.
-
1
module DataStreaming
-
1
extend ActiveSupport::Concern
-
-
1
include ActionController::Rendering
-
-
1
DEFAULT_SEND_FILE_TYPE = 'application/octet-stream'.freeze #:nodoc:
-
1
DEFAULT_SEND_FILE_DISPOSITION = 'attachment'.freeze #:nodoc:
-
-
1
protected
-
# Sends the file. This uses a server-appropriate method (such as X-Sendfile)
-
# via the Rack::Sendfile middleware. The header to use is set via
-
# +config.action_dispatch.x_sendfile_header+.
-
# Your server can also configure this for you by setting the X-Sendfile-Type header.
-
#
-
# Be careful to sanitize the path parameter if it is coming from a web
-
# page. <tt>send_file(params[:path])</tt> allows a malicious user to
-
# download any file on your server.
-
#
-
# Options:
-
# * <tt>:filename</tt> - suggests a filename for the browser to use.
-
# Defaults to <tt>File.basename(path)</tt>.
-
# * <tt>:type</tt> - specifies an HTTP content type.
-
# You can specify either a string or a symbol for a registered type register with
-
# <tt>Mime::Type.register</tt>, for example :json
-
# If omitted, type will be guessed from the file extension specified in <tt>:filename</tt>.
-
# If no content type is registered for the extension, default type 'application/octet-stream' will be used.
-
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
-
# Valid values are 'inline' and 'attachment' (default).
-
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to 200.
-
# * <tt>:url_based_filename</tt> - set to +true+ if you want the browser guess the filename from
-
# the URL, which is necessary for i18n filenames on certain browsers
-
# (setting <tt>:filename</tt> overrides this option).
-
#
-
# The default Content-Type and Content-Disposition headers are
-
# set to download arbitrary binary files in as many browsers as
-
# possible. IE versions 4, 5, 5.5, and 6 are all known to have
-
# a variety of quirks (especially when downloading over SSL).
-
#
-
# Simple download:
-
#
-
# send_file '/path/to.zip'
-
#
-
# Show a JPEG in the browser:
-
#
-
# send_file '/path/to.jpeg', type: 'image/jpeg', disposition: 'inline'
-
#
-
# Show a 404 page in the browser:
-
#
-
# send_file '/path/to/404.html', type: 'text/html; charset=utf-8', status: 404
-
#
-
# Read about the other Content-* HTTP headers if you'd like to
-
# provide the user with more information (such as Content-Description) in
-
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11.
-
#
-
# Also be aware that the document may be cached by proxies and browsers.
-
# The Pragma and Cache-Control headers declare how the file may be cached
-
# by intermediaries. They default to require clients to validate with
-
# the server before releasing cached responses. See
-
# http://www.mnot.net/cache_docs/ for an overview of web caching and
-
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
-
# for the Cache-Control header spec.
-
1
def send_file(path, options = {}) #:doc:
-
raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path)
-
-
options[:filename] ||= File.basename(path) unless options[:url_based_filename]
-
send_file_headers! options
-
-
self.status = options[:status] || 200
-
self.content_type = options[:content_type] if options.key?(:content_type)
-
self.response_body = FileBody.new(path)
-
end
-
-
# Avoid having to pass an open file handle as the response body.
-
# Rack::Sendfile will usually intercept the response and uses
-
# the path directly, so there is no reason to open the file.
-
1
class FileBody #:nodoc:
-
1
attr_reader :to_path
-
-
1
def initialize(path)
-
@to_path = path
-
end
-
-
# Stream the file's contents if Rack::Sendfile isn't present.
-
1
def each
-
File.open(to_path, 'rb') do |file|
-
while chunk = file.read(16384)
-
yield chunk
-
end
-
end
-
end
-
end
-
-
# Sends the given binary data to the browser. This method is similar to
-
# <tt>render plain: data</tt>, but also allows you to specify whether
-
# the browser should display the response as a file attachment (i.e. in a
-
# download dialog) or as inline data. You may also set the content type,
-
# the apparent file name, and other things.
-
#
-
# Options:
-
# * <tt>:filename</tt> - suggests a filename for the browser to use.
-
# * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify
-
# either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json
-
# If omitted, type will be guessed from the file extension specified in <tt>:filename</tt>.
-
# If no content type is registered for the extension, default type 'application/octet-stream' will be used.
-
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
-
# Valid values are 'inline' and 'attachment' (default).
-
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to 200.
-
#
-
# Generic data download:
-
#
-
# send_data buffer
-
#
-
# Download a dynamically-generated tarball:
-
#
-
# send_data generate_tgz('dir'), filename: 'dir.tgz'
-
#
-
# Display an image Active Record in the browser:
-
#
-
# send_data image.data, type: image.content_type, disposition: 'inline'
-
#
-
# See +send_file+ for more information on HTTP Content-* headers and caching.
-
1
def send_data(data, options = {}) #:doc:
-
send_file_headers! options
-
render options.slice(:status, :content_type).merge(:text => data)
-
end
-
-
1
private
-
1
def send_file_headers!(options)
-
type_provided = options.has_key?(:type)
-
-
content_type = options.fetch(:type, DEFAULT_SEND_FILE_TYPE)
-
raise ArgumentError, ":type option required" if content_type.nil?
-
-
if content_type.is_a?(Symbol)
-
extension = Mime[content_type]
-
raise ArgumentError, "Unknown MIME type #{options[:type]}" unless extension
-
self.content_type = extension
-
else
-
if !type_provided && options[:filename]
-
# If type wasn't provided, try guessing from file extension.
-
content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.delete('.')) || content_type
-
end
-
self.content_type = content_type
-
end
-
-
disposition = options.fetch(:disposition, DEFAULT_SEND_FILE_DISPOSITION)
-
unless disposition.nil?
-
disposition = disposition.to_s
-
disposition += %(; filename="#{options[:filename]}") if options[:filename]
-
headers['Content-Disposition'] = disposition
-
end
-
-
headers['Content-Transfer-Encoding'] = 'binary'
-
-
response.sending_file = true
-
-
# Fix a problem with IE 6.0 on opening downloaded files:
-
# If Cache-Control: no-cache is set (which Rails does by default),
-
# IE removes the file it just downloaded from its cache immediately
-
# after it displays the "open/save" dialog, which means that if you
-
# hit "open" the file isn't there anymore when the application that
-
# is called for handling the download is run, so let's workaround that
-
response.cache_control[:public] ||= false
-
end
-
end
-
end
-
1
module ActionController
-
# When our views change, they should bubble up into HTTP cache freshness
-
# and bust browser caches. So the template digest for the current action
-
# is automatically included in the ETag.
-
#
-
# Enabled by default for apps that use Action View. Disable by setting
-
#
-
# config.action_controller.etag_with_template_digest = false
-
#
-
# Override the template to digest by passing +:template+ to +fresh_when+
-
# and +stale?+ calls. For example:
-
#
-
# # We're going to render widgets/show, not posts/show
-
# fresh_when @post, template: 'widgets/show'
-
#
-
# # We're not going to render a template, so omit it from the ETag.
-
# fresh_when @post, template: false
-
#
-
1
module EtagWithTemplateDigest
-
1
extend ActiveSupport::Concern
-
-
1
include ActionController::ConditionalGet
-
-
1
included do
-
1
class_attribute :etag_with_template_digest
-
1
self.etag_with_template_digest = true
-
-
1
ActiveSupport.on_load :action_view, yield: true do |action_view_base|
-
1
etag do |options|
-
determine_template_etag(options) if etag_with_template_digest
-
end
-
end
-
end
-
-
1
private
-
1
def determine_template_etag(options)
-
if template = pick_template_for_etag(options)
-
lookup_and_digest_template(template)
-
end
-
end
-
-
1
def pick_template_for_etag(options)
-
options.fetch(:template) { "#{controller_name}/#{action_name}" }
-
end
-
-
1
def lookup_and_digest_template(template)
-
ActionView::Digestor.digest name: template, finder: lookup_context
-
end
-
end
-
end
-
1
module ActionController #:nodoc:
-
1
module Flash
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
class_attribute :_flash_types, instance_accessor: false
-
1
self._flash_types = []
-
-
1
delegate :flash, to: :request
-
1
add_flash_types(:alert, :notice)
-
end
-
-
1
module ClassMethods
-
# Creates new flash types. You can pass as many types as you want to create
-
# flash types other than the default <tt>alert</tt> and <tt>notice</tt> in
-
# your controllers and views. For instance:
-
#
-
# # in application_controller.rb
-
# class ApplicationController < ActionController::Base
-
# add_flash_types :warning
-
# end
-
#
-
# # in your controller
-
# redirect_to user_path(@user), warning: "Incomplete profile"
-
#
-
# # in your view
-
# <%= warning %>
-
#
-
# This method will automatically define a new method for each of the given
-
# names, and it will be available in your views.
-
1
def add_flash_types(*types)
-
1
types.each do |type|
-
2
next if _flash_types.include?(type)
-
-
2
define_method(type) do
-
request.flash[type]
-
end
-
2
helper_method type
-
-
2
self._flash_types += [type]
-
end
-
end
-
end
-
-
1
protected
-
1
def redirect_to(options = {}, response_status_and_flash = {}) #:doc:
-
7
self.class._flash_types.each do |flash_type|
-
14
if type = response_status_and_flash.delete(flash_type)
-
3
flash[flash_type] = type
-
end
-
end
-
-
7
if other_flashes = response_status_and_flash.delete(:flash)
-
flash.update(other_flashes)
-
end
-
-
7
super(options, response_status_and_flash)
-
end
-
end
-
end
-
1
require 'active_support/core_ext/hash/except'
-
1
require 'active_support/core_ext/hash/slice'
-
-
1
module ActionController
-
# This module provides a method which will redirect browser to use HTTPS
-
# protocol. This will ensure that user's sensitive information will be
-
# transferred safely over the internet. You _should_ always force browser
-
# to use HTTPS when you're transferring sensitive information such as
-
# user authentication, account information, or credit card information.
-
#
-
# Note that if you are really concerned about your application security,
-
# you might consider using +config.force_ssl+ in your config file instead.
-
# That will ensure all the data transferred via HTTPS protocol and prevent
-
# user from getting session hijacked when accessing the site under unsecured
-
# HTTP protocol.
-
1
module ForceSSL
-
1
extend ActiveSupport::Concern
-
1
include AbstractController::Callbacks
-
-
1
ACTION_OPTIONS = [:only, :except, :if, :unless]
-
1
URL_OPTIONS = [:protocol, :host, :domain, :subdomain, :port, :path]
-
1
REDIRECT_OPTIONS = [:status, :flash, :alert, :notice]
-
-
1
module ClassMethods
-
# Force the request to this particular controller or specified actions to be
-
# under HTTPS protocol.
-
#
-
# If you need to disable this for any reason (e.g. development) then you can use
-
# an +:if+ or +:unless+ condition.
-
#
-
# class AccountsController < ApplicationController
-
# force_ssl if: :ssl_configured?
-
#
-
# def ssl_configured?
-
# !Rails.env.development?
-
# end
-
# end
-
#
-
# ==== URL Options
-
# You can pass any of the following options to affect the redirect url
-
# * <tt>host</tt> - Redirect to a different host name
-
# * <tt>subdomain</tt> - Redirect to a different subdomain
-
# * <tt>domain</tt> - Redirect to a different domain
-
# * <tt>port</tt> - Redirect to a non-standard port
-
# * <tt>path</tt> - Redirect to a different path
-
#
-
# ==== Redirect Options
-
# You can pass any of the following options to affect the redirect status and response
-
# * <tt>status</tt> - Redirect with a custom status (default is 301 Moved Permanently)
-
# * <tt>flash</tt> - Set a flash message when redirecting
-
# * <tt>alert</tt> - Set an alert message when redirecting
-
# * <tt>notice</tt> - Set a notice message when redirecting
-
#
-
# ==== Action Options
-
# You can pass any of the following options to affect the before_action callback
-
# * <tt>only</tt> - The callback should be run only for this action
-
# * <tt>except</tt> - The callback should be run for all actions except this action
-
# * <tt>if</tt> - A symbol naming an instance method or a proc; the callback
-
# will be called only when it returns a true value.
-
# * <tt>unless</tt> - A symbol naming an instance method or a proc; the callback
-
# will be called only when it returns a false value.
-
1
def force_ssl(options = {})
-
action_options = options.slice(*ACTION_OPTIONS)
-
redirect_options = options.except(*ACTION_OPTIONS)
-
before_action(action_options) do
-
force_ssl_redirect(redirect_options)
-
end
-
end
-
end
-
-
# Redirect the existing request to use the HTTPS protocol.
-
#
-
# ==== Parameters
-
# * <tt>host_or_options</tt> - Either a host name or any of the url & redirect options
-
# available to the <tt>force_ssl</tt> method.
-
1
def force_ssl_redirect(host_or_options = nil)
-
unless request.ssl?
-
options = {
-
:protocol => 'https://',
-
:host => request.host,
-
:path => request.fullpath,
-
:status => :moved_permanently
-
}
-
-
if host_or_options.is_a?(Hash)
-
options.merge!(host_or_options)
-
elsif host_or_options
-
options[:host] = host_or_options
-
end
-
-
secure_url = ActionDispatch::Http::URL.url_for(options.slice(*URL_OPTIONS))
-
flash.keep if respond_to?(:flash)
-
redirect_to secure_url, options.slice(*REDIRECT_OPTIONS)
-
end
-
end
-
end
-
end
-
1
module ActionController
-
1
module Head
-
# Returns a response that has no content (merely headers). The options
-
# argument is interpreted to be a hash of header names and values.
-
# This allows you to easily return a response that consists only of
-
# significant headers:
-
#
-
# head :created, location: person_path(@person)
-
#
-
# head :created, location: @person
-
#
-
# It can also be used to return exceptional conditions:
-
#
-
# return head(:method_not_allowed) unless request.post?
-
# return head(:bad_request) unless valid_request?
-
# render
-
#
-
# See Rack::Utils::SYMBOL_TO_STATUS_CODE for a full list of valid +status+ symbols.
-
1
def head(status, options = {})
-
options, status = status, nil if status.is_a?(Hash)
-
status ||= options.delete(:status) || :ok
-
location = options.delete(:location)
-
content_type = options.delete(:content_type)
-
-
options.each do |key, value|
-
headers[key.to_s.dasherize.split('-').each { |v| v[0] = v[0].chr.upcase }.join('-')] = value.to_s
-
end
-
-
self.status = status
-
self.location = url_for(location) if location
-
-
self.response_body = ""
-
-
if include_content?(self.response_code)
-
self.content_type = content_type || (Mime[formats.first] if formats)
-
self.response.charset = false if self.response
-
else
-
headers.delete('Content-Type')
-
headers.delete('Content-Length')
-
end
-
-
true
-
end
-
-
1
private
-
# :nodoc:
-
1
def include_content?(status)
-
case status
-
when 100..199
-
false
-
when 204, 205, 304
-
false
-
else
-
true
-
end
-
end
-
end
-
end
-
-
1
module ActionController
-
# Adds the ability to prevent public methods on a controller to be called as actions.
-
1
module HideActions
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
class_attribute :hidden_actions
-
1
self.hidden_actions = Set.new.freeze
-
end
-
-
1
private
-
-
# Overrides AbstractController::Base#action_method? to return false if the
-
# action name is in the list of hidden actions.
-
1
def method_for_action(action_name)
-
16
self.class.visible_action?(action_name) && super
-
end
-
-
1
module ClassMethods
-
# Sets all of the actions passed in as hidden actions.
-
#
-
# ==== Parameters
-
# * <tt>args</tt> - A list of actions
-
1
def hide_action(*args)
-
self.hidden_actions = hidden_actions.dup.merge(args.map(&:to_s)).freeze
-
end
-
-
1
def visible_action?(action_name)
-
16
not hidden_actions.include?(action_name)
-
end
-
-
# Overrides AbstractController::Base#action_methods to remove any methods
-
# that are listed as hidden methods.
-
1
def action_methods
-
172
@action_methods ||= Set.new(super.reject { |name| hidden_actions.include?(name) }).freeze
-
end
-
end
-
end
-
end
-
1
require 'base64'
-
-
1
module ActionController
-
# Makes it dead easy to do HTTP Basic, Digest and Token authentication.
-
1
module HttpAuthentication
-
# Makes it dead easy to do HTTP \Basic authentication.
-
#
-
# === Simple \Basic example
-
#
-
# class PostsController < ApplicationController
-
# http_basic_authenticate_with name: "dhh", password: "secret", except: :index
-
#
-
# def index
-
# render plain: "Everyone can see me!"
-
# end
-
#
-
# def edit
-
# render plain: "I'm only accessible if you know the password"
-
# end
-
# end
-
#
-
# === Advanced \Basic example
-
#
-
# Here is a more advanced \Basic example where only Atom feeds and the XML API is protected by HTTP authentication,
-
# the regular HTML interface is protected by a session approach:
-
#
-
# class ApplicationController < ActionController::Base
-
# before_action :set_account, :authenticate
-
#
-
# protected
-
# def set_account
-
# @account = Account.find_by(url_name: request.subdomains.first)
-
# end
-
#
-
# def authenticate
-
# case request.format
-
# when Mime::XML, Mime::ATOM
-
# if user = authenticate_with_http_basic { |u, p| @account.users.authenticate(u, p) }
-
# @current_user = user
-
# else
-
# request_http_basic_authentication
-
# end
-
# else
-
# if session_authenticated?
-
# @current_user = @account.users.find(session[:authenticated][:user_id])
-
# else
-
# redirect_to(login_url) and return false
-
# end
-
# end
-
# end
-
# end
-
#
-
# In your integration tests, you can do something like this:
-
#
-
# def test_access_granted_from_xml
-
# @request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
-
# get "/notes/1.xml"
-
#
-
# assert_equal 200, status
-
# end
-
1
module Basic
-
1
extend self
-
-
1
module ControllerMethods
-
1
extend ActiveSupport::Concern
-
-
1
module ClassMethods
-
1
def http_basic_authenticate_with(options = {})
-
before_action(options.except(:name, :password, :realm)) do
-
authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password|
-
name == options[:name] && password == options[:password]
-
end
-
end
-
end
-
end
-
-
1
def authenticate_or_request_with_http_basic(realm = "Application", &login_procedure)
-
authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm)
-
end
-
-
1
def authenticate_with_http_basic(&login_procedure)
-
HttpAuthentication::Basic.authenticate(request, &login_procedure)
-
end
-
-
1
def request_http_basic_authentication(realm = "Application")
-
HttpAuthentication::Basic.authentication_request(self, realm)
-
end
-
end
-
-
1
def authenticate(request, &login_procedure)
-
if has_basic_credentials?(request)
-
login_procedure.call(*user_name_and_password(request))
-
end
-
end
-
-
1
def has_basic_credentials?(request)
-
request.authorization.present? && (auth_scheme(request) == 'Basic')
-
end
-
-
1
def user_name_and_password(request)
-
decode_credentials(request).split(':', 2)
-
end
-
-
1
def decode_credentials(request)
-
::Base64.decode64(auth_param(request) || '')
-
end
-
-
1
def auth_scheme(request)
-
request.authorization.split(' ', 2).first
-
end
-
-
1
def auth_param(request)
-
request.authorization.split(' ', 2).second
-
end
-
-
1
def encode_credentials(user_name, password)
-
"Basic #{::Base64.strict_encode64("#{user_name}:#{password}")}"
-
end
-
-
1
def authentication_request(controller, realm)
-
controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.gsub(/"/, "")}")
-
controller.status = 401
-
controller.response_body = "HTTP Basic: Access denied.\n"
-
end
-
end
-
-
# Makes it dead easy to do HTTP \Digest authentication.
-
#
-
# === Simple \Digest example
-
#
-
# require 'digest/md5'
-
# class PostsController < ApplicationController
-
# REALM = "SuperSecret"
-
# USERS = {"dhh" => "secret", #plain text password
-
# "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
-
#
-
# before_action :authenticate, except: [:index]
-
#
-
# def index
-
# render plain: "Everyone can see me!"
-
# end
-
#
-
# def edit
-
# render plain: "I'm only accessible if you know the password"
-
# end
-
#
-
# private
-
# def authenticate
-
# authenticate_or_request_with_http_digest(REALM) do |username|
-
# USERS[username]
-
# end
-
# end
-
# end
-
#
-
# === Notes
-
#
-
# The +authenticate_or_request_with_http_digest+ block must return the user's password
-
# or the ha1 digest hash so the framework can appropriately hash to check the user's
-
# credentials. Returning +nil+ will cause authentication to fail.
-
#
-
# Storing the ha1 hash: MD5(username:realm:password), is better than storing a plain password. If
-
# the password file or database is compromised, the attacker would be able to use the ha1 hash to
-
# authenticate as the user at this +realm+, but would not have the user's password to try using at
-
# other sites.
-
#
-
# In rare instances, web servers or front proxies strip authorization headers before
-
# they reach your application. You can debug this situation by logging all environment
-
# variables, and check for HTTP_AUTHORIZATION, amongst others.
-
1
module Digest
-
1
extend self
-
-
1
module ControllerMethods
-
1
def authenticate_or_request_with_http_digest(realm = "Application", &password_procedure)
-
authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm)
-
end
-
-
# Authenticate with HTTP Digest, returns true or false
-
1
def authenticate_with_http_digest(realm = "Application", &password_procedure)
-
HttpAuthentication::Digest.authenticate(request, realm, &password_procedure)
-
end
-
-
# Render output including the HTTP Digest authentication header
-
1
def request_http_digest_authentication(realm = "Application", message = nil)
-
HttpAuthentication::Digest.authentication_request(self, realm, message)
-
end
-
end
-
-
# Returns false on a valid response, true otherwise
-
1
def authenticate(request, realm, &password_procedure)
-
request.authorization && validate_digest_response(request, realm, &password_procedure)
-
end
-
-
# Returns false unless the request credentials response value matches the expected value.
-
# First try the password as a ha1 digest password. If this fails, then try it as a plain
-
# text password.
-
1
def validate_digest_response(request, realm, &password_procedure)
-
secret_key = secret_token(request)
-
credentials = decode_credentials_header(request)
-
valid_nonce = validate_nonce(secret_key, request, credentials[:nonce])
-
-
if valid_nonce && realm == credentials[:realm] && opaque(secret_key) == credentials[:opaque]
-
password = password_procedure.call(credentials[:username])
-
return false unless password
-
-
method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD']
-
uri = credentials[:uri]
-
-
[true, false].any? do |trailing_question_mark|
-
[true, false].any? do |password_is_ha1|
-
_uri = trailing_question_mark ? uri + "?" : uri
-
expected = expected_response(method, _uri, credentials, password, password_is_ha1)
-
expected == credentials[:response]
-
end
-
end
-
end
-
end
-
-
# Returns the expected response for a request of +http_method+ to +uri+ with the decoded +credentials+ and the expected +password+
-
# Optional parameter +password_is_ha1+ is set to +true+ by default, since best practice is to store ha1 digest instead
-
# of a plain-text password.
-
1
def expected_response(http_method, uri, credentials, password, password_is_ha1=true)
-
ha1 = password_is_ha1 ? password : ha1(credentials, password)
-
ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(':'))
-
::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(':'))
-
end
-
-
1
def ha1(credentials, password)
-
::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(':'))
-
end
-
-
1
def encode_credentials(http_method, credentials, password, password_is_ha1)
-
credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password, password_is_ha1)
-
"Digest " + credentials.sort_by {|x| x[0].to_s }.map {|v| "#{v[0]}='#{v[1]}'" }.join(', ')
-
end
-
-
1
def decode_credentials_header(request)
-
decode_credentials(request.authorization)
-
end
-
-
1
def decode_credentials(header)
-
ActiveSupport::HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/, '').split(',').map do |pair|
-
key, value = pair.split('=', 2)
-
[key.strip, value.to_s.gsub(/^"|"$/,'').delete('\'')]
-
end]
-
end
-
-
1
def authentication_header(controller, realm)
-
secret_key = secret_token(controller.request)
-
nonce = self.nonce(secret_key)
-
opaque = opaque(secret_key)
-
controller.headers["WWW-Authenticate"] = %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce}", opaque="#{opaque}")
-
end
-
-
1
def authentication_request(controller, realm, message = nil)
-
message ||= "HTTP Digest: Access denied.\n"
-
authentication_header(controller, realm)
-
controller.status = 401
-
controller.response_body = message
-
end
-
-
1
def secret_token(request)
-
key_generator = request.env["action_dispatch.key_generator"]
-
http_auth_salt = request.env["action_dispatch.http_auth_salt"]
-
key_generator.generate_key(http_auth_salt)
-
end
-
-
# Uses an MD5 digest based on time to generate a value to be used only once.
-
#
-
# A server-specified data string which should be uniquely generated each time a 401 response is made.
-
# It is recommended that this string be base64 or hexadecimal data.
-
# Specifically, since the string is passed in the header lines as a quoted string, the double-quote character is not allowed.
-
#
-
# The contents of the nonce are implementation dependent.
-
# The quality of the implementation depends on a good choice.
-
# A nonce might, for example, be constructed as the base 64 encoding of
-
#
-
# time-stamp H(time-stamp ":" ETag ":" private-key)
-
#
-
# where time-stamp is a server-generated time or other non-repeating value,
-
# ETag is the value of the HTTP ETag header associated with the requested entity,
-
# and private-key is data known only to the server.
-
# With a nonce of this form a server would recalculate the hash portion after receiving the client authentication header and
-
# reject the request if it did not match the nonce from that header or
-
# if the time-stamp value is not recent enough. In this way the server can limit the time of the nonce's validity.
-
# The inclusion of the ETag prevents a replay request for an updated version of the resource.
-
# (Note: including the IP address of the client in the nonce would appear to offer the server the ability
-
# to limit the reuse of the nonce to the same client that originally got it.
-
# However, that would break proxy farms, where requests from a single user often go through different proxies in the farm.
-
# Also, IP address spoofing is not that hard.)
-
#
-
# An implementation might choose not to accept a previously used nonce or a previously used digest, in order to
-
# protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for
-
# POST, PUT, or PATCH requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
-
# of this document.
-
#
-
# The nonce is opaque to the client. Composed of Time, and hash of Time with secret
-
# key from the Rails session secret generated upon creation of project. Ensures
-
# the time cannot be modified by client.
-
1
def nonce(secret_key, time = Time.now)
-
t = time.to_i
-
hashed = [t, secret_key]
-
digest = ::Digest::MD5.hexdigest(hashed.join(":"))
-
::Base64.strict_encode64("#{t}:#{digest}")
-
end
-
-
# Might want a shorter timeout depending on whether the request
-
# is a PATCH, PUT, or POST, and if client is browser or web service.
-
# Can be much shorter if the Stale directive is implemented. This would
-
# allow a user to use new nonce without prompting user again for their
-
# username and password.
-
1
def validate_nonce(secret_key, request, value, seconds_to_timeout=5*60)
-
return false if value.nil?
-
t = ::Base64.decode64(value).split(":").first.to_i
-
nonce(secret_key, t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout
-
end
-
-
# Opaque based on random generation - but changing each request?
-
1
def opaque(secret_key)
-
::Digest::MD5.hexdigest(secret_key)
-
end
-
-
end
-
-
# Makes it dead easy to do HTTP Token authentication.
-
#
-
# Simple Token example:
-
#
-
# class PostsController < ApplicationController
-
# TOKEN = "secret"
-
#
-
# before_action :authenticate, except: [ :index ]
-
#
-
# def index
-
# render plain: "Everyone can see me!"
-
# end
-
#
-
# def edit
-
# render plain: "I'm only accessible if you know the password"
-
# end
-
#
-
# private
-
# def authenticate
-
# authenticate_or_request_with_http_token do |token, options|
-
# token == TOKEN
-
# end
-
# end
-
# end
-
#
-
#
-
# Here is a more advanced Token example where only Atom feeds and the XML API is protected by HTTP token authentication,
-
# the regular HTML interface is protected by a session approach:
-
#
-
# class ApplicationController < ActionController::Base
-
# before_action :set_account, :authenticate
-
#
-
# protected
-
# def set_account
-
# @account = Account.find_by(url_name: request.subdomains.first)
-
# end
-
#
-
# def authenticate
-
# case request.format
-
# when Mime::XML, Mime::ATOM
-
# if user = authenticate_with_http_token { |t, o| @account.users.authenticate(t, o) }
-
# @current_user = user
-
# else
-
# request_http_token_authentication
-
# end
-
# else
-
# if session_authenticated?
-
# @current_user = @account.users.find(session[:authenticated][:user_id])
-
# else
-
# redirect_to(login_url) and return false
-
# end
-
# end
-
# end
-
# end
-
#
-
#
-
# In your integration tests, you can do something like this:
-
#
-
# def test_access_granted_from_xml
-
# get(
-
# "/notes/1.xml", nil,
-
# 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token)
-
# )
-
#
-
# assert_equal 200, status
-
# end
-
#
-
#
-
# On shared hosts, Apache sometimes doesn't pass authentication headers to
-
# FCGI instances. If your environment matches this description and you cannot
-
# authenticate, try this rule in your Apache setup:
-
#
-
# RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
-
1
module Token
-
1
TOKEN_KEY = 'token='
-
1
TOKEN_REGEX = /^Token /
-
1
AUTHN_PAIR_DELIMITERS = /(?:,|;|\t+)/
-
1
extend self
-
-
1
module ControllerMethods
-
1
def authenticate_or_request_with_http_token(realm = "Application", &login_procedure)
-
authenticate_with_http_token(&login_procedure) || request_http_token_authentication(realm)
-
end
-
-
1
def authenticate_with_http_token(&login_procedure)
-
Token.authenticate(self, &login_procedure)
-
end
-
-
1
def request_http_token_authentication(realm = "Application")
-
Token.authentication_request(self, realm)
-
end
-
end
-
-
# If token Authorization header is present, call the login
-
# procedure with the present token and options.
-
#
-
# [controller]
-
# ActionController::Base instance for the current request.
-
#
-
# [login_procedure]
-
# Proc to call if a token is present. The Proc should take two arguments:
-
#
-
# authenticate(controller) { |token, options| ... }
-
#
-
# Returns the return value of <tt>login_procedure</tt> if a
-
# token is found. Returns <tt>nil</tt> if no token is found.
-
-
1
def authenticate(controller, &login_procedure)
-
token, options = token_and_options(controller.request)
-
unless token.blank?
-
login_procedure.call(token, options)
-
end
-
end
-
-
# Parses the token and options out of the token authorization header. If
-
# the header looks like this:
-
# Authorization: Token token="abc", nonce="def"
-
# Then the returned token is "abc", and the options is {nonce: "def"}
-
#
-
# request - ActionDispatch::Request instance with the current headers.
-
#
-
# Returns an Array of [String, Hash] if a token is present.
-
# Returns nil if no token is found.
-
1
def token_and_options(request)
-
authorization_request = request.authorization.to_s
-
if authorization_request[TOKEN_REGEX]
-
params = token_params_from authorization_request
-
[params.shift[1], Hash[params].with_indifferent_access]
-
end
-
end
-
-
1
def token_params_from(auth)
-
rewrite_param_values params_array_from raw_params auth
-
end
-
-
# Takes raw_params and turns it into an array of parameters
-
1
def params_array_from(raw_params)
-
raw_params.map { |param| param.split %r/=(.+)?/ }
-
end
-
-
# This removes the <tt>"</tt> characters wrapping the value.
-
1
def rewrite_param_values(array_params)
-
array_params.each { |param| (param[1] || "").gsub! %r/^"|"$/, '' }
-
end
-
-
# This method takes an authorization body and splits up the key-value
-
# pairs by the standardized <tt>:</tt>, <tt>;</tt>, or <tt>\t</tt>
-
# delimiters defined in +AUTHN_PAIR_DELIMITERS+.
-
1
def raw_params(auth)
-
_raw_params = auth.sub(TOKEN_REGEX, '').split(/\s*#{AUTHN_PAIR_DELIMITERS}\s*/)
-
-
if !(_raw_params.first =~ %r{\A#{TOKEN_KEY}})
-
_raw_params[0] = "#{TOKEN_KEY}#{_raw_params.first}"
-
end
-
-
_raw_params
-
end
-
-
# Encodes the given token and options into an Authorization header value.
-
#
-
# token - String token.
-
# options - optional Hash of the options.
-
#
-
# Returns String.
-
1
def encode_credentials(token, options = {})
-
values = ["#{TOKEN_KEY}#{token.to_s.inspect}"] + options.map do |key, value|
-
"#{key}=#{value.to_s.inspect}"
-
end
-
"Token #{values * ", "}"
-
end
-
-
# Sets a WWW-Authenticate to let the client know a token is desired.
-
#
-
# controller - ActionController::Base instance for the outgoing response.
-
# realm - String realm to use in the header.
-
#
-
# Returns nothing.
-
1
def authentication_request(controller, realm)
-
controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.gsub(/"/, "")}")
-
controller.__send__ :render, :text => "HTTP Token: Access denied.\n", :status => :unauthorized
-
end
-
end
-
end
-
end
-
1
module ActionController
-
1
module ImplicitRender
-
1
def send_action(method, *args)
-
16
ret = super
-
16
default_render unless performed?
-
16
ret
-
end
-
-
1
def default_render(*args)
-
7
render(*args)
-
end
-
-
1
def method_for_action(action_name)
-
super || if template_exists?(action_name.to_s, _prefixes)
-
"default_render"
-
16
end
-
end
-
end
-
end
-
1
require 'benchmark'
-
1
require 'abstract_controller/logger'
-
-
1
module ActionController
-
# Adds instrumentation to several ends in ActionController::Base. It also provides
-
# some hooks related with process_action, this allows an ORM like Active Record
-
# and/or DataMapper to plug in ActionController and show related information.
-
#
-
# Check ActiveRecord::Railties::ControllerRuntime for an example.
-
1
module Instrumentation
-
1
extend ActiveSupport::Concern
-
-
1
include AbstractController::Logger
-
1
include ActionController::RackDelegation
-
-
1
attr_internal :view_runtime
-
-
1
def process_action(*args)
-
16
raw_payload = {
-
:controller => self.class.name,
-
:action => self.action_name,
-
:params => request.filtered_parameters,
-
:format => request.format.try(:ref),
-
:method => request.request_method,
-
16
:path => (request.fullpath rescue "unknown")
-
}
-
-
16
ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup)
-
-
16
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
-
16
begin
-
16
result = super
-
16
payload[:status] = response.status
-
16
result
-
ensure
-
16
append_info_to_payload(payload)
-
end
-
end
-
end
-
-
1
def render(*args)
-
9
render_output = nil
-
9
self.view_runtime = cleanup_view_runtime do
-
18
Benchmark.ms { render_output = super }
-
end
-
9
render_output
-
end
-
-
1
def send_file(path, options={})
-
ActiveSupport::Notifications.instrument("send_file.action_controller",
-
options.merge(:path => path)) do
-
super
-
end
-
end
-
-
1
def send_data(data, options = {})
-
ActiveSupport::Notifications.instrument("send_data.action_controller", options) do
-
super
-
end
-
end
-
-
1
def redirect_to(*args)
-
7
ActiveSupport::Notifications.instrument("redirect_to.action_controller") do |payload|
-
7
result = super
-
7
payload[:status] = response.status
-
7
payload[:location] = response.filtered_location
-
7
result
-
end
-
end
-
-
1
private
-
-
# A hook invoked every time a before callback is halted.
-
1
def halted_callback_hook(filter)
-
ActiveSupport::Notifications.instrument("halted_callback.action_controller", :filter => filter)
-
end
-
-
# A hook which allows you to clean up any time taken into account in
-
# views wrongly, like database querying time.
-
#
-
# def cleanup_view_runtime
-
# super - time_taken_in_something_expensive
-
# end
-
#
-
# :api: plugin
-
1
def cleanup_view_runtime #:nodoc:
-
9
yield
-
end
-
-
# Every time after an action is processed, this method is invoked
-
# with the payload, so you can add more information.
-
# :api: plugin
-
1
def append_info_to_payload(payload) #:nodoc:
-
16
payload[:view_runtime] = view_runtime
-
end
-
-
1
module ClassMethods
-
# A hook which allows other frameworks to log what happened during
-
# controller process action. This method should return an array
-
# with the messages to be added.
-
# :api: plugin
-
1
def log_process_action(payload) #:nodoc:
-
16
messages, view_runtime = [], payload[:view_runtime]
-
16
messages << ("Views: %.1fms" % view_runtime.to_f) if view_runtime
-
16
messages
-
end
-
end
-
end
-
end
-
1
require 'abstract_controller/collector'
-
-
1
module ActionController #:nodoc:
-
1
module MimeResponds
-
1
extend ActiveSupport::Concern
-
-
# :stopdoc:
-
1
module ClassMethods
-
1
def respond_to(*)
-
raise NoMethodError, "The controller-level `respond_to' feature has " \
-
"been extracted to the `responders` gem. Add it to your Gemfile to " \
-
"continue using this feature:\n" \
-
" gem 'responders', '~> 2.0'\n" \
-
"Consult the Rails upgrade guide for details."
-
end
-
end
-
-
1
def respond_with(*)
-
raise NoMethodError, "The `respond_with' feature has been extracted " \
-
"to the `responders` gem. Add it to your Gemfile to continue using " \
-
"this feature:\n" \
-
" gem 'responders', '~> 2.0'\n" \
-
"Consult the Rails upgrade guide for details."
-
end
-
# :startdoc:
-
-
# Without web-service support, an action which collects the data for displaying a list of people
-
# might look something like this:
-
#
-
# def index
-
# @people = Person.all
-
# end
-
#
-
# Here's the same action, with web-service support baked in:
-
#
-
# def index
-
# @people = Person.all
-
#
-
# respond_to do |format|
-
# format.html
-
# format.xml { render xml: @people }
-
# end
-
# end
-
#
-
# What that says is, "if the client wants HTML in response to this action, just respond as we
-
# would have before, but if the client wants XML, return them the list of people in XML format."
-
# (Rails determines the desired response format from the HTTP Accept header submitted by the client.)
-
#
-
# Supposing you have an action that adds a new person, optionally creating their company
-
# (by name) if it does not already exist, without web-services, it might look like this:
-
#
-
# def create
-
# @company = Company.find_or_create_by(name: params[:company][:name])
-
# @person = @company.people.create(params[:person])
-
#
-
# redirect_to(person_list_url)
-
# end
-
#
-
# Here's the same action, with web-service support baked in:
-
#
-
# def create
-
# company = params[:person].delete(:company)
-
# @company = Company.find_or_create_by(name: company[:name])
-
# @person = @company.people.create(params[:person])
-
#
-
# respond_to do |format|
-
# format.html { redirect_to(person_list_url) }
-
# format.js
-
# format.xml { render xml: @person.to_xml(include: @company) }
-
# end
-
# end
-
#
-
# If the client wants HTML, we just redirect them back to the person list. If they want JavaScript,
-
# then it is an Ajax request and we render the JavaScript template associated with this action.
-
# Lastly, if the client wants XML, we render the created person as XML, but with a twist: we also
-
# include the person's company in the rendered XML, so you get something like this:
-
#
-
# <person>
-
# <id>...</id>
-
# ...
-
# <company>
-
# <id>...</id>
-
# <name>...</name>
-
# ...
-
# </company>
-
# </person>
-
#
-
# Note, however, the extra bit at the top of that action:
-
#
-
# company = params[:person].delete(:company)
-
# @company = Company.find_or_create_by(name: company[:name])
-
#
-
# This is because the incoming XML document (if a web-service request is in process) can only contain a
-
# single root-node. So, we have to rearrange things so that the request looks like this (url-encoded):
-
#
-
# person[name]=...&person[company][name]=...&...
-
#
-
# And, like this (xml-encoded):
-
#
-
# <person>
-
# <name>...</name>
-
# <company>
-
# <name>...</name>
-
# </company>
-
# </person>
-
#
-
# In other words, we make the request so that it operates on a single entity's person. Then, in the action,
-
# we extract the company data from the request, find or create the company, and then create the new person
-
# with the remaining data.
-
#
-
# Note that you can define your own XML parameter parser which would allow you to describe multiple entities
-
# in a single request (i.e., by wrapping them all in a single root node), but if you just go with the flow
-
# and accept Rails' defaults, life will be much easier.
-
#
-
# If you need to use a MIME type which isn't supported by default, you can register your own handlers in
-
# config/initializers/mime_types.rb as follows.
-
#
-
# Mime::Type.register "image/jpg", :jpg
-
#
-
# Respond to also allows you to specify a common block for different formats by using any:
-
#
-
# def index
-
# @people = Person.all
-
#
-
# respond_to do |format|
-
# format.html
-
# format.any(:xml, :json) { render request.format.to_sym => @people }
-
# end
-
# end
-
#
-
# In the example above, if the format is xml, it will render:
-
#
-
# render xml: @people
-
#
-
# Or if the format is json:
-
#
-
# render json: @people
-
#
-
# Formats can have different variants.
-
#
-
# The request variant is a specialization of the request format, like <tt>:tablet</tt>,
-
# <tt>:phone</tt>, or <tt>:desktop</tt>.
-
#
-
# We often want to render different html/json/xml templates for phones,
-
# tablets, and desktop browsers. Variants make it easy.
-
#
-
# You can set the variant in a +before_action+:
-
#
-
# request.variant = :tablet if request.user_agent =~ /iPad/
-
#
-
# Respond to variants in the action just like you respond to formats:
-
#
-
# respond_to do |format|
-
# format.html do |variant|
-
# variant.tablet # renders app/views/projects/show.html+tablet.erb
-
# variant.phone { extra_setup; render ... }
-
# variant.none { special_setup } # executed only if there is no variant set
-
# end
-
# end
-
#
-
# Provide separate templates for each format and variant:
-
#
-
# app/views/projects/show.html.erb
-
# app/views/projects/show.html+tablet.erb
-
# app/views/projects/show.html+phone.erb
-
#
-
# When you're not sharing any code within the format, you can simplify defining variants
-
# using the inline syntax:
-
#
-
# respond_to do |format|
-
# format.js { render "trash" }
-
# format.html.phone { redirect_to progress_path }
-
# format.html.none { render "trash" }
-
# end
-
#
-
# Variants also support common `any`/`all` block that formats have.
-
#
-
# It works for both inline:
-
#
-
# respond_to do |format|
-
# format.html.any { render text: "any" }
-
# format.html.phone { render text: "phone" }
-
# end
-
#
-
# and block syntax:
-
#
-
# respond_to do |format|
-
# format.html do |variant|
-
# variant.any(:tablet, :phablet){ render text: "any" }
-
# variant.phone { render text: "phone" }
-
# end
-
# end
-
#
-
# You can also set an array of variants:
-
#
-
# request.variant = [:tablet, :phone]
-
#
-
# which will work similarly to formats and MIME types negotiation. If there will be no
-
# :tablet variant declared, :phone variant will be picked:
-
#
-
# respond_to do |format|
-
# format.html.none
-
# format.html.phone # this gets rendered
-
# end
-
#
-
# Be sure to check the documentation of <tt>ActionController::MimeResponds.respond_to</tt>
-
# for more examples.
-
1
def respond_to(*mimes)
-
3
raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?
-
-
3
collector = Collector.new(mimes, request.variant)
-
3
yield collector if block_given?
-
-
3
if format = collector.negotiate_format(request)
-
3
_process_format(format)
-
3
response = collector.response
-
3
response ? response.call : render({})
-
else
-
raise ActionController::UnknownFormat
-
end
-
end
-
-
# A container for responses available from the current controller for
-
# requests for different mime-types sent to a particular action.
-
#
-
# The public controller methods +respond_to+ may be called with a block
-
# that is used to define responses to different mime-types, e.g.
-
# for +respond_to+ :
-
#
-
# respond_to do |format|
-
# format.html
-
# format.xml { render xml: @people }
-
# end
-
#
-
# In this usage, the argument passed to the block (+format+ above) is an
-
# instance of the ActionController::MimeResponds::Collector class. This
-
# object serves as a container in which available responses can be stored by
-
# calling any of the dynamically generated, mime-type-specific methods such
-
# as +html+, +xml+ etc on the Collector. Each response is represented by a
-
# corresponding block if present.
-
#
-
# A subsequent call to #negotiate_format(request) will enable the Collector
-
# to determine which specific mime-type it should respond with for the current
-
# request, with this response then being accessible by calling #response.
-
1
class Collector
-
1
include AbstractController::Collector
-
1
attr_accessor :format
-
-
1
def initialize(mimes, variant = nil)
-
3
@responses = {}
-
3
@variant = variant
-
-
3
mimes.each { |mime| @responses["Mime::#{mime.upcase}".constantize] = nil }
-
end
-
-
1
def any(*args, &block)
-
if args.any?
-
args.each { |type| send(type, &block) }
-
else
-
custom(Mime::ALL, &block)
-
end
-
end
-
1
alias :all :any
-
-
1
def custom(mime_type, &block)
-
5
mime_type = Mime::Type.lookup(mime_type.to_s) unless mime_type.is_a?(Mime::Type)
-
5
@responses[mime_type] ||= if block_given?
-
5
block
-
else
-
VariantCollector.new(@variant)
-
end
-
end
-
-
1
def response
-
3
response = @responses.fetch(format, @responses[Mime::ALL])
-
3
if response.is_a?(VariantCollector) # `format.html.phone` - variant inline syntax
-
response.variant
-
3
elsif response.nil? || response.arity == 0 # `format.html` - just a format, call its block
-
3
response
-
else # `format.html{ |variant| variant.phone }` - variant block syntax
-
variant_collector = VariantCollector.new(@variant)
-
response.call(variant_collector) # call format block with variants collector
-
variant_collector.variant
-
end
-
end
-
-
1
def negotiate_format(request)
-
3
@format = request.negotiate_mime(@responses.keys)
-
end
-
-
1
class VariantCollector #:nodoc:
-
1
def initialize(variant = nil)
-
@variant = variant
-
@variants = {}
-
end
-
-
1
def any(*args, &block)
-
if block_given?
-
if args.any? && args.none?{ |a| a == @variant }
-
args.each{ |v| @variants[v] = block }
-
else
-
@variants[:any] = block
-
end
-
end
-
end
-
1
alias :all :any
-
-
1
def method_missing(name, *args, &block)
-
@variants[name] = block if block_given?
-
end
-
-
1
def variant
-
if @variant.nil?
-
@variants[:none] || @variants[:any]
-
elsif (@variants.keys & @variant).any?
-
@variant.each do |v|
-
return @variants[v] if @variants.key?(v)
-
end
-
else
-
@variants[:any]
-
end
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/hash/slice'
-
1
require 'active_support/core_ext/hash/except'
-
1
require 'active_support/core_ext/module/anonymous'
-
1
require 'active_support/core_ext/struct'
-
1
require 'action_dispatch/http/mime_type'
-
-
1
module ActionController
-
# Wraps the parameters hash into a nested hash. This will allow clients to
-
# submit requests without having to specify any root elements.
-
#
-
# This functionality is enabled in +config/initializers/wrap_parameters.rb+
-
# and can be customized. If you are upgrading to \Rails 3.1, this file will
-
# need to be created for the functionality to be enabled.
-
#
-
# You could also turn it on per controller by setting the format array to
-
# a non-empty array:
-
#
-
# class UsersController < ApplicationController
-
# wrap_parameters format: [:json, :xml, :url_encoded_form, :multipart_form]
-
# end
-
#
-
# If you enable +ParamsWrapper+ for +:json+ format, instead of having to
-
# send JSON parameters like this:
-
#
-
# {"user": {"name": "Konata"}}
-
#
-
# You can send parameters like this:
-
#
-
# {"name": "Konata"}
-
#
-
# And it will be wrapped into a nested hash with the key name matching the
-
# controller's name. For example, if you're posting to +UsersController+,
-
# your new +params+ hash will look like this:
-
#
-
# {"name" => "Konata", "user" => {"name" => "Konata"}}
-
#
-
# You can also specify the key in which the parameters should be wrapped to,
-
# and also the list of attributes it should wrap by using either +:include+ or
-
# +:exclude+ options like this:
-
#
-
# class UsersController < ApplicationController
-
# wrap_parameters :person, include: [:username, :password]
-
# end
-
#
-
# On ActiveRecord models with no +:include+ or +:exclude+ option set,
-
# it will only wrap the parameters returned by the class method
-
# <tt>attribute_names</tt>.
-
#
-
# If you're going to pass the parameters to an +ActiveModel+ object (such as
-
# <tt>User.new(params[:user])</tt>), you might consider passing the model class to
-
# the method instead. The +ParamsWrapper+ will actually try to determine the
-
# list of attribute names from the model and only wrap those attributes:
-
#
-
# class UsersController < ApplicationController
-
# wrap_parameters Person
-
# end
-
#
-
# You still could pass +:include+ and +:exclude+ to set the list of attributes
-
# you want to wrap.
-
#
-
# By default, if you don't specify the key in which the parameters would be
-
# wrapped to, +ParamsWrapper+ will actually try to determine if there's
-
# a model related to it or not. This controller, for example:
-
#
-
# class Admin::UsersController < ApplicationController
-
# end
-
#
-
# will try to check if <tt>Admin::User</tt> or +User+ model exists, and use it to
-
# determine the wrapper key respectively. If both models don't exist,
-
# it will then fallback to use +user+ as the key.
-
1
module ParamsWrapper
-
1
extend ActiveSupport::Concern
-
-
1
EXCLUDE_PARAMETERS = %w(authenticity_token _method utf8)
-
-
1
require 'mutex_m'
-
-
1
class Options < Struct.new(:name, :format, :include, :exclude, :klass, :model) # :nodoc:
-
1
include Mutex_m
-
-
1
def self.from_hash(hash)
-
2
name = hash[:name]
-
2
format = Array(hash[:format])
-
2
include = hash[:include] && Array(hash[:include]).collect(&:to_s)
-
2
exclude = hash[:exclude] && Array(hash[:exclude]).collect(&:to_s)
-
2
new name, format, include, exclude, nil, nil
-
end
-
-
1
def initialize(name, format, include, exclude, klass, model) # nodoc
-
2
super
-
2
@include_set = include
-
2
@name_set = name
-
end
-
-
1
def model
-
super || synchronize { super || self.model = _default_wrap_model }
-
end
-
-
1
def include
-
return super if @include_set
-
-
m = model
-
synchronize do
-
return super if @include_set
-
-
@include_set = true
-
-
unless super || exclude
-
if m.respond_to?(:attribute_names) && m.attribute_names.any?
-
self.include = m.attribute_names
-
end
-
end
-
end
-
end
-
-
1
def name
-
return super if @name_set
-
-
m = model
-
synchronize do
-
return super if @name_set
-
-
@name_set = true
-
-
unless super || klass.anonymous?
-
self.name = m ? m.to_s.demodulize.underscore :
-
klass.controller_name.singularize
-
end
-
end
-
end
-
-
1
private
-
# Determine the wrapper model from the controller's name. By convention,
-
# this could be done by trying to find the defined model that has the
-
# same singularize name as the controller. For example, +UsersController+
-
# will try to find if the +User+ model exists.
-
#
-
# This method also does namespace lookup. Foo::Bar::UsersController will
-
# try to find Foo::Bar::User, Foo::User and finally User.
-
1
def _default_wrap_model #:nodoc:
-
return nil if klass.anonymous?
-
model_name = klass.name.sub(/Controller$/, '').classify
-
-
begin
-
if model_klass = model_name.safe_constantize
-
model_klass
-
else
-
namespaces = model_name.split("::")
-
namespaces.delete_at(-2)
-
break if namespaces.last == model_name
-
model_name = namespaces.join("::")
-
end
-
end until model_klass
-
-
model_klass
-
end
-
end
-
-
1
included do
-
1
class_attribute :_wrapper_options
-
1
self._wrapper_options = Options.from_hash(format: [])
-
end
-
-
1
module ClassMethods
-
1
def _set_wrapper_options(options)
-
self._wrapper_options = Options.from_hash(options)
-
end
-
-
# Sets the name of the wrapper key, or the model which +ParamsWrapper+
-
# would use to determine the attribute names from.
-
#
-
# ==== Examples
-
# wrap_parameters format: :xml
-
# # enables the parameter wrapper for XML format
-
#
-
# wrap_parameters :person
-
# # wraps parameters into +params[:person]+ hash
-
#
-
# wrap_parameters Person
-
# # wraps parameters by determining the wrapper key from Person class
-
# (+person+, in this case) and the list of attribute names
-
#
-
# wrap_parameters include: [:username, :title]
-
# # wraps only +:username+ and +:title+ attributes from parameters.
-
#
-
# wrap_parameters false
-
# # disables parameters wrapping for this controller altogether.
-
#
-
# ==== Options
-
# * <tt>:format</tt> - The list of formats in which the parameters wrapper
-
# will be enabled.
-
# * <tt>:include</tt> - The list of attribute names which parameters wrapper
-
# will wrap into a nested hash.
-
# * <tt>:exclude</tt> - The list of attribute names which parameters wrapper
-
# will exclude from a nested hash.
-
1
def wrap_parameters(name_or_model_or_options, options = {})
-
1
model = nil
-
-
1
case name_or_model_or_options
-
when Hash
-
1
options = name_or_model_or_options
-
when false
-
options = options.merge(:format => [])
-
when Symbol, String
-
options = options.merge(:name => name_or_model_or_options)
-
else
-
model = name_or_model_or_options
-
end
-
-
1
opts = Options.from_hash _wrapper_options.to_h.slice(:format).merge(options)
-
1
opts.model = model
-
1
opts.klass = self
-
-
1
self._wrapper_options = opts
-
end
-
-
# Sets the default wrapper key or model which will be used to determine
-
# wrapper key and attribute names. Will be called automatically when the
-
# module is inherited.
-
1
def inherited(klass)
-
5
if klass._wrapper_options.format.any?
-
5
params = klass._wrapper_options.dup
-
5
params.klass = klass
-
5
klass._wrapper_options = params
-
end
-
5
super
-
end
-
end
-
-
# Performs parameters wrapping upon the request. Will be called automatically
-
# by the metal call stack.
-
1
def process_action(*args)
-
16
if _wrapper_enabled?
-
if request.parameters[_wrapper_key].present?
-
wrapped_hash = _extract_parameters(request.parameters)
-
else
-
wrapped_hash = _wrap_parameters request.request_parameters
-
end
-
-
wrapped_keys = request.request_parameters.keys
-
wrapped_filtered_hash = _wrap_parameters request.filtered_parameters.slice(*wrapped_keys)
-
-
# This will make the wrapped hash accessible from controller and view
-
request.parameters.merge! wrapped_hash
-
request.request_parameters.merge! wrapped_hash
-
-
# This will display the wrapped hash in the log file
-
request.filtered_parameters.merge! wrapped_filtered_hash
-
end
-
16
super
-
end
-
-
1
private
-
-
# Returns the wrapper key which will be used to stored wrapped parameters.
-
1
def _wrapper_key
-
_wrapper_options.name
-
end
-
-
# Returns the list of enabled formats.
-
1
def _wrapper_formats
-
16
_wrapper_options.format
-
end
-
-
# Returns the list of parameters which will be selected for wrapped.
-
1
def _wrap_parameters(parameters)
-
{ _wrapper_key => _extract_parameters(parameters) }
-
end
-
-
1
def _extract_parameters(parameters)
-
if include_only = _wrapper_options.include
-
parameters.slice(*include_only)
-
else
-
exclude = _wrapper_options.exclude || []
-
parameters.except(*(exclude + EXCLUDE_PARAMETERS))
-
end
-
end
-
-
# Checks if we should perform parameters wrapping.
-
1
def _wrapper_enabled?
-
16
ref = request.content_mime_type.try(:ref)
-
16
_wrapper_formats.include?(ref) && _wrapper_key && !request.request_parameters[_wrapper_key]
-
end
-
end
-
end
-
1
require 'action_dispatch/http/request'
-
1
require 'action_dispatch/http/response'
-
-
1
module ActionController
-
1
module RackDelegation
-
1
extend ActiveSupport::Concern
-
-
1
delegate :headers, :status=, :location=, :content_type=,
-
:status, :location, :content_type, :response_code, :to => "@_response"
-
-
1
def dispatch(action, request)
-
set_response!(request)
-
super(action, request)
-
end
-
-
1
def response_body=(body)
-
16
response.body = body if response
-
16
super
-
end
-
-
1
def reset_session
-
@_request.reset_session
-
end
-
-
1
private
-
-
1
def set_response!(request)
-
@_response = ActionDispatch::Response.new
-
@_response.request = request
-
end
-
end
-
end
-
1
module ActionController
-
1
class RedirectBackError < AbstractController::Error #:nodoc:
-
1
DEFAULT_MESSAGE = 'No HTTP_REFERER was set in the request to this action, so redirect_to :back could not be called successfully. If this is a test, make sure to specify request.env["HTTP_REFERER"].'
-
-
1
def initialize(message = nil)
-
super(message || DEFAULT_MESSAGE)
-
end
-
end
-
-
1
module Redirecting
-
1
extend ActiveSupport::Concern
-
-
1
include AbstractController::Logger
-
1
include ActionController::RackDelegation
-
1
include ActionController::UrlFor
-
-
# Redirects the browser to the target specified in +options+. This parameter can be any one of:
-
#
-
# * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+.
-
# * <tt>Record</tt> - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record.
-
# * <tt>String</tt> starting with <tt>protocol://</tt> (like <tt>http://</tt>) or a protocol relative reference (like <tt>//</tt>) - Is passed straight through as the target for redirection.
-
# * <tt>String</tt> not containing a protocol - The current protocol and host is prepended to the string.
-
# * <tt>Proc</tt> - A block that will be executed in the controller's context. Should return any option accepted by +redirect_to+.
-
# * <tt>:back</tt> - Back to the page that issued the request. Useful for forms that are triggered from multiple places.
-
# Short-hand for <tt>redirect_to(request.env["HTTP_REFERER"])</tt>
-
#
-
# === Examples:
-
#
-
# redirect_to action: "show", id: 5
-
# redirect_to post
-
# redirect_to "http://www.rubyonrails.org"
-
# redirect_to "/images/screenshot.jpg"
-
# redirect_to articles_url
-
# redirect_to :back
-
# redirect_to proc { edit_post_url(@post) }
-
#
-
# The redirection happens as a "302 Found" header unless otherwise specified using the <tt>:status</tt> option:
-
#
-
# redirect_to post_url(@post), status: :found
-
# redirect_to action: 'atom', status: :moved_permanently
-
# redirect_to post_url(@post), status: 301
-
# redirect_to action: 'atom', status: 302
-
#
-
# The status code can either be a standard {HTTP Status code}[http://www.iana.org/assignments/http-status-codes] as an
-
# integer, or a symbol representing the downcased, underscored and symbolized description.
-
# Note that the status code must be a 3xx HTTP code, or redirection will not occur.
-
#
-
# If you are using XHR requests other than GET or POST and redirecting after the
-
# request then some browsers will follow the redirect using the original request
-
# method. This may lead to undesirable behavior such as a double DELETE. To work
-
# around this you can return a <tt>303 See Other</tt> status code which will be
-
# followed using a GET request.
-
#
-
# redirect_to posts_url, status: :see_other
-
# redirect_to action: 'index', status: 303
-
#
-
# It is also possible to assign a flash message as part of the redirection. There are two special accessors for the commonly used flash names
-
# +alert+ and +notice+ as well as a general purpose +flash+ bucket.
-
#
-
# redirect_to post_url(@post), alert: "Watch it, mister!"
-
# redirect_to post_url(@post), status: :found, notice: "Pay attention to the road"
-
# redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id }
-
# redirect_to({ action: 'atom' }, alert: "Something serious happened")
-
#
-
# When using <tt>redirect_to :back</tt>, if there is no referrer,
-
# <tt>ActionController::RedirectBackError</tt> will be raised. You
-
# may specify some fallback behavior for this case by rescuing
-
# <tt>ActionController::RedirectBackError</tt>.
-
1
def redirect_to(options = {}, response_status = {}) #:doc:
-
7
raise ActionControllerError.new("Cannot redirect to nil!") unless options
-
7
raise ActionControllerError.new("Cannot redirect to a parameter hash!") if options.is_a?(ActionController::Parameters)
-
7
raise AbstractController::DoubleRenderError if response_body
-
-
7
self.status = _extract_redirect_to_status(options, response_status)
-
7
self.location = _compute_redirect_to_location(request, options)
-
7
self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(location)}\">redirected</a>.</body></html>"
-
end
-
-
1
def _compute_redirect_to_location(request, options) #:nodoc:
-
case options
-
# The scheme name consist of a letter followed by any combination of
-
# letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
-
# characters; and is terminated by a colon (":").
-
# See http://tools.ietf.org/html/rfc3986#section-3.1
-
# The protocol relative scheme starts with a double slash "//".
-
when /\A([a-z][a-z\d\-+\.]*:|\/\/).*/i
-
5
options
-
when String
-
request.protocol + request.host_with_port + options
-
when :back
-
request.headers["Referer"] or raise RedirectBackError
-
when Proc
-
_compute_redirect_to_location request, options.call
-
else
-
6
url_for(options)
-
11
end.delete("\0\r\n")
-
end
-
1
module_function :_compute_redirect_to_location
-
1
public :_compute_redirect_to_location
-
-
1
private
-
1
def _extract_redirect_to_status(options, response_status)
-
7
if options.is_a?(Hash) && options.key?(:status)
-
Rack::Utils.status_code(options.delete(:status))
-
7
elsif response_status.key?(:status)
-
Rack::Utils.status_code(response_status[:status])
-
else
-
7
302
-
end
-
end
-
end
-
end
-
1
require 'set'
-
-
1
module ActionController
-
# See <tt>Renderers.add</tt>
-
1
def self.add_renderer(key, &block)
-
Renderers.add(key, &block)
-
end
-
-
# See <tt>Renderers.remove</tt>
-
1
def self.remove_renderer(key)
-
Renderers.remove(key)
-
end
-
-
1
class MissingRenderer < LoadError
-
1
def initialize(format)
-
super "No renderer defined for format: #{format}"
-
end
-
end
-
-
1
module Renderers
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
class_attribute :_renderers
-
1
self._renderers = Set.new.freeze
-
end
-
-
1
module ClassMethods
-
1
def use_renderers(*args)
-
renderers = _renderers + args
-
self._renderers = renderers.freeze
-
end
-
1
alias use_renderer use_renderers
-
end
-
-
1
def render_to_body(options)
-
9
_render_to_body_with_renderer(options) || super
-
end
-
-
1
def _render_to_body_with_renderer(options)
-
9
_renderers.each do |name|
-
27
if options.key?(name)
-
_process_options(options)
-
method_name = Renderers._render_with_renderer_method_name(name)
-
return send(method_name, options.delete(name), options)
-
end
-
end
-
nil
-
end
-
-
# A Set containing renderer names that correspond to available renderer procs.
-
# Default values are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>.
-
1
RENDERERS = Set.new
-
-
1
def self._render_with_renderer_method_name(key)
-
3
"_render_with_renderer_#{key}"
-
end
-
-
# Adds a new renderer to call within controller actions.
-
# A renderer is invoked by passing its name as an option to
-
# <tt>AbstractController::Rendering#render</tt>. To create a renderer
-
# pass it a name and a block. The block takes two arguments, the first
-
# is the value paired with its key and the second is the remaining
-
# hash of options passed to +render+.
-
#
-
# Create a csv renderer:
-
#
-
# ActionController::Renderers.add :csv do |obj, options|
-
# filename = options[:filename] || 'data'
-
# str = obj.respond_to?(:to_csv) ? obj.to_csv : obj.to_s
-
# send_data str, type: Mime::CSV,
-
# disposition: "attachment; filename=#{filename}.csv"
-
# end
-
#
-
# Note that we used Mime::CSV for the csv mime type as it comes with Rails.
-
# For a custom renderer, you'll need to register a mime type with
-
# <tt>Mime::Type.register</tt>.
-
#
-
# To use the csv renderer in a controller action:
-
#
-
# def show
-
# @csvable = Csvable.find(params[:id])
-
# respond_to do |format|
-
# format.html
-
# format.csv { render csv: @csvable, filename: @csvable.name }
-
# end
-
# end
-
# To use renderers and their mime types in more concise ways, see
-
# <tt>ActionController::MimeResponds::ClassMethods.respond_to</tt>
-
1
def self.add(key, &block)
-
3
define_method(_render_with_renderer_method_name(key), &block)
-
3
RENDERERS << key.to_sym
-
end
-
-
# This method is the opposite of add method.
-
#
-
# Usage:
-
#
-
# ActionController::Renderers.remove(:csv)
-
1
def self.remove(key)
-
RENDERERS.delete(key.to_sym)
-
method_name = _render_with_renderer_method_name(key)
-
remove_method(method_name) if method_defined?(method_name)
-
end
-
-
1
module All
-
1
extend ActiveSupport::Concern
-
1
include Renderers
-
-
1
included do
-
1
self._renderers = RENDERERS
-
end
-
end
-
-
1
add :json do |json, options|
-
json = json.to_json(options) unless json.kind_of?(String)
-
-
if options[:callback].present?
-
if content_type.nil? || content_type == Mime::JSON
-
self.content_type = Mime::JS
-
end
-
-
"/**/#{options[:callback]}(#{json})"
-
else
-
self.content_type ||= Mime::JSON
-
json
-
end
-
end
-
-
1
add :js do |js, options|
-
self.content_type ||= Mime::JS
-
js.respond_to?(:to_js) ? js.to_js(options) : js
-
end
-
-
1
add :xml do |xml, options|
-
self.content_type ||= Mime::XML
-
xml.respond_to?(:to_xml) ? xml.to_xml(options) : xml
-
end
-
end
-
end
-
1
module ActionController
-
1
module Rendering
-
1
extend ActiveSupport::Concern
-
-
1
RENDER_FORMATS_IN_PRIORITY = [:body, :text, :plain, :html]
-
-
# Before processing, set the request formats in current controller formats.
-
1
def process_action(*) #:nodoc:
-
16
self.formats = request.formats.map(&:ref).compact
-
16
super
-
end
-
-
# Check for double render errors and set the content_type after rendering.
-
1
def render(*args) #:nodoc:
-
9
raise ::AbstractController::DoubleRenderError if self.response_body
-
9
super
-
end
-
-
# Overwrite render_to_string because body can now be set to a rack body.
-
1
def render_to_string(*)
-
result = super
-
if result.respond_to?(:each)
-
string = ""
-
result.each { |r| string << r }
-
string
-
else
-
result
-
end
-
end
-
-
1
def render_to_body(options = {})
-
9
super || _render_in_priorities(options) || ' '
-
end
-
-
1
private
-
-
1
def _render_in_priorities(options)
-
RENDER_FORMATS_IN_PRIORITY.each do |format|
-
return options[format] if options.key?(format)
-
end
-
-
nil
-
end
-
-
1
def _process_format(format, options = {})
-
12
super
-
-
12
if options[:plain]
-
self.content_type = Mime::TEXT
-
else
-
12
self.content_type ||= format.to_s
-
end
-
end
-
-
# Normalize arguments by catching blocks and setting them on :update.
-
1
def _normalize_args(action=nil, options={}, &blk) #:nodoc:
-
9
options = super
-
9
options[:update] = blk if block_given?
-
9
options
-
end
-
-
# Normalize both text and status options.
-
1
def _normalize_options(options) #:nodoc:
-
9
_normalize_text(options)
-
-
9
if options[:html]
-
options[:html] = ERB::Util.html_escape(options[:html])
-
end
-
-
9
if options.delete(:nothing)
-
options[:body] = nil
-
end
-
-
9
if options[:status]
-
options[:status] = Rack::Utils.status_code(options[:status])
-
end
-
-
9
super
-
end
-
-
1
def _normalize_text(options)
-
9
RENDER_FORMATS_IN_PRIORITY.each do |format|
-
36
if options.key?(format) && options[format].respond_to?(:to_text)
-
options[format] = options[format].to_text
-
end
-
end
-
end
-
-
# Process controller specific options, as status, content-type and location.
-
1
def _process_options(options) #:nodoc:
-
9
status, content_type, location = options.values_at(:status, :content_type, :location)
-
-
9
self.status = status if status
-
9
self.content_type = content_type if content_type
-
9
self.headers["Location"] = url_for(location) if location
-
-
9
super
-
end
-
end
-
end
-
1
require 'rack/session/abstract/id'
-
1
require 'action_controller/metal/exceptions'
-
1
require 'active_support/security_utils'
-
-
1
module ActionController #:nodoc:
-
1
class InvalidAuthenticityToken < ActionControllerError #:nodoc:
-
end
-
-
1
class InvalidCrossOriginRequest < ActionControllerError #:nodoc:
-
end
-
-
# Controller actions are protected from Cross-Site Request Forgery (CSRF) attacks
-
# by including a token in the rendered HTML for your application. This token is
-
# stored as a random string in the session, to which an attacker does not have
-
# access. When a request reaches your application, \Rails verifies the received
-
# token with the token in the session. Only HTML and JavaScript requests are checked,
-
# so this will not protect your XML API (presumably you'll have a different
-
# authentication scheme there anyway).
-
#
-
# GET requests are not protected since they don't have side effects like writing
-
# to the database and don't leak sensitive information. JavaScript requests are
-
# an exception: a third-party site can use a <script> tag to reference a JavaScript
-
# URL on your site. When your JavaScript response loads on their site, it executes.
-
# With carefully crafted JavaScript on their end, sensitive data in your JavaScript
-
# response may be extracted. To prevent this, only XmlHttpRequest (known as XHR or
-
# Ajax) requests are allowed to make GET requests for JavaScript responses.
-
#
-
# It's important to remember that XML or JSON requests are also affected and if
-
# you're building an API you'll need something like:
-
#
-
# class ApplicationController < ActionController::Base
-
# protect_from_forgery
-
# skip_before_action :verify_authenticity_token, if: :json_request?
-
#
-
# protected
-
#
-
# def json_request?
-
# request.format.json?
-
# end
-
# end
-
#
-
# CSRF protection is turned on with the <tt>protect_from_forgery</tt> method,
-
# which checks the token and resets the session if it doesn't match what was expected.
-
# A call to this method is generated for new \Rails applications by default.
-
#
-
# The token parameter is named <tt>authenticity_token</tt> by default. The name and
-
# value of this token must be added to every layout that renders forms by including
-
# <tt>csrf_meta_tags</tt> in the HTML +head+.
-
#
-
# Learn more about CSRF attacks and securing your application in the
-
# {Ruby on Rails Security Guide}[http://guides.rubyonrails.org/security.html].
-
1
module RequestForgeryProtection
-
1
extend ActiveSupport::Concern
-
-
1
include AbstractController::Helpers
-
1
include AbstractController::Callbacks
-
-
1
included do
-
# Sets the token parameter name for RequestForgery. Calling +protect_from_forgery+
-
# sets it to <tt>:authenticity_token</tt> by default.
-
1
config_accessor :request_forgery_protection_token
-
1
self.request_forgery_protection_token ||= :authenticity_token
-
-
# Holds the class which implements the request forgery protection.
-
1
config_accessor :forgery_protection_strategy
-
1
self.forgery_protection_strategy = nil
-
-
# Controls whether request forgery protection is turned on or not. Turned off by default only in test mode.
-
1
config_accessor :allow_forgery_protection
-
1
self.allow_forgery_protection = true if allow_forgery_protection.nil?
-
-
# Controls whether a CSRF failure logs a warning. On by default.
-
1
config_accessor :log_warning_on_csrf_failure
-
1
self.log_warning_on_csrf_failure = true
-
-
1
helper_method :form_authenticity_token
-
1
helper_method :protect_against_forgery?
-
end
-
-
1
module ClassMethods
-
# Turn on request forgery protection. Bear in mind that GET and HEAD requests are not checked.
-
#
-
# class ApplicationController < ActionController::Base
-
# protect_from_forgery
-
# end
-
#
-
# class FooController < ApplicationController
-
# protect_from_forgery except: :index
-
#
-
# You can disable CSRF protection on controller by skipping the verification before_action:
-
# skip_before_action :verify_authenticity_token
-
#
-
# Valid Options:
-
#
-
# * <tt>:only/:except</tt> - Passed to the <tt>before_action</tt> call. Set which actions are verified.
-
# * <tt>:with</tt> - Set the method to handle unverified request.
-
#
-
# Valid unverified request handling methods are:
-
# * <tt>:exception</tt> - Raises ActionController::InvalidAuthenticityToken exception.
-
# * <tt>:reset_session</tt> - Resets the session.
-
# * <tt>:null_session</tt> - Provides an empty session during request but doesn't reset it completely. Used as default if <tt>:with</tt> option is not specified.
-
1
def protect_from_forgery(options = {})
-
1
self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session)
-
1
self.request_forgery_protection_token ||= :authenticity_token
-
1
prepend_before_action :verify_authenticity_token, options
-
1
append_after_action :verify_same_origin_request
-
end
-
-
1
private
-
-
1
def protection_method_class(name)
-
1
ActionController::RequestForgeryProtection::ProtectionMethods.const_get(name.to_s.classify)
-
rescue NameError
-
raise ArgumentError, 'Invalid request forgery protection method, use :null_session, :exception, or :reset_session'
-
end
-
end
-
-
1
module ProtectionMethods
-
1
class NullSession
-
1
def initialize(controller)
-
@controller = controller
-
end
-
-
# This is the method that defines the application behavior when a request is found to be unverified.
-
1
def handle_unverified_request
-
request = @controller.request
-
request.session = NullSessionHash.new(request.env)
-
request.env['action_dispatch.request.flash_hash'] = nil
-
request.env['rack.session.options'] = { skip: true }
-
request.env['action_dispatch.cookies'] = NullCookieJar.build(request)
-
end
-
-
1
protected
-
-
1
class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc:
-
1
def initialize(env)
-
super(nil, env)
-
@data = {}
-
@loaded = true
-
end
-
-
# no-op
-
1
def destroy; end
-
-
1
def exists?
-
true
-
end
-
end
-
-
1
class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc:
-
1
def self.build(request)
-
key_generator = request.env[ActionDispatch::Cookies::GENERATOR_KEY]
-
host = request.host
-
secure = request.ssl?
-
-
new(key_generator, host, secure, options_for_env({}))
-
end
-
-
1
def write(*)
-
# nothing
-
end
-
end
-
end
-
-
1
class ResetSession
-
1
def initialize(controller)
-
@controller = controller
-
end
-
-
1
def handle_unverified_request
-
@controller.reset_session
-
end
-
end
-
-
1
class Exception
-
1
def initialize(controller)
-
@controller = controller
-
end
-
-
1
def handle_unverified_request
-
raise ActionController::InvalidAuthenticityToken
-
end
-
end
-
end
-
-
1
protected
-
# The actual before_action that is used to verify the CSRF token.
-
# Don't override this directly. Provide your own forgery protection
-
# strategy instead. If you override, you'll disable same-origin
-
# `<script>` verification.
-
#
-
# Lean on the protect_from_forgery declaration to mark which actions are
-
# due for same-origin request verification. If protect_from_forgery is
-
# enabled on an action, this before_action flags its after_action to
-
# verify that JavaScript responses are for XHR requests, ensuring they
-
# follow the browser's same-origin policy.
-
1
def verify_authenticity_token
-
16
mark_for_same_origin_verification!
-
-
16
if !verified_request?
-
if logger && log_warning_on_csrf_failure
-
logger.warn "Can't verify CSRF token authenticity"
-
end
-
handle_unverified_request
-
end
-
end
-
-
1
def handle_unverified_request
-
forgery_protection_strategy.new(self).handle_unverified_request
-
end
-
-
#:nodoc:
-
1
CROSS_ORIGIN_JAVASCRIPT_WARNING = "Security warning: an embedded " \
-
"<script> tag on another site requested protected JavaScript. " \
-
"If you know what you're doing, go ahead and disable forgery " \
-
"protection on this action to permit cross-origin JavaScript embedding."
-
1
private_constant :CROSS_ORIGIN_JAVASCRIPT_WARNING
-
-
# If `verify_authenticity_token` was run (indicating that we have
-
# forgery protection enabled for this request) then also verify that
-
# we aren't serving an unauthorized cross-origin response.
-
1
def verify_same_origin_request
-
16
if marked_for_same_origin_verification? && non_xhr_javascript_response?
-
logger.warn CROSS_ORIGIN_JAVASCRIPT_WARNING if logger
-
raise ActionController::InvalidCrossOriginRequest, CROSS_ORIGIN_JAVASCRIPT_WARNING
-
end
-
end
-
-
# GET requests are checked for cross-origin JavaScript after rendering.
-
1
def mark_for_same_origin_verification!
-
16
@marked_for_same_origin_verification = request.get?
-
end
-
-
# If the `verify_authenticity_token` before_action ran, verify that
-
# JavaScript responses are only served to same-origin GET requests.
-
1
def marked_for_same_origin_verification?
-
16
@marked_for_same_origin_verification ||= false
-
end
-
-
# Check for cross-origin JavaScript responses.
-
1
def non_xhr_javascript_response?
-
9
content_type =~ %r(\Atext/javascript) && !request.xhr?
-
end
-
-
1
AUTHENTICITY_TOKEN_LENGTH = 32
-
-
# Returns true or false if a request is verified. Checks:
-
#
-
# * is it a GET or HEAD request? Gets should be safe and idempotent
-
# * Does the form_authenticity_token match the given token value from the params?
-
# * Does the X-CSRF-Token header match the form_authenticity_token
-
1
def verified_request?
-
16
!protect_against_forgery? || request.get? || request.head? ||
-
valid_authenticity_token?(session, form_authenticity_param) ||
-
valid_authenticity_token?(session, request.headers['X-CSRF-Token'])
-
end
-
-
# Sets the token value for the current session.
-
1
def form_authenticity_token
-
masked_authenticity_token(session)
-
end
-
-
# Creates a masked version of the authenticity token that varies
-
# on each request. The masking is used to mitigate SSL attacks
-
# like BREACH.
-
1
def masked_authenticity_token(session)
-
one_time_pad = SecureRandom.random_bytes(AUTHENTICITY_TOKEN_LENGTH)
-
encrypted_csrf_token = xor_byte_strings(one_time_pad, real_csrf_token(session))
-
masked_token = one_time_pad + encrypted_csrf_token
-
Base64.strict_encode64(masked_token)
-
end
-
-
# Checks the client's masked token to see if it matches the
-
# session token. Essentially the inverse of
-
# +masked_authenticity_token+.
-
1
def valid_authenticity_token?(session, encoded_masked_token)
-
if encoded_masked_token.nil? || encoded_masked_token.empty? || !encoded_masked_token.is_a?(String)
-
return false
-
end
-
-
begin
-
masked_token = Base64.strict_decode64(encoded_masked_token)
-
rescue ArgumentError # encoded_masked_token is invalid Base64
-
return false
-
end
-
-
# See if it's actually a masked token or not. In order to
-
# deploy this code, we should be able to handle any unmasked
-
# tokens that we've issued without error.
-
-
if masked_token.length == AUTHENTICITY_TOKEN_LENGTH
-
# This is actually an unmasked token. This is expected if
-
# you have just upgraded to masked tokens, but should stop
-
# happening shortly after installing this gem
-
compare_with_real_token masked_token, session
-
-
elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2
-
# Split the token into the one-time pad and the encrypted
-
# value and decrypt it
-
one_time_pad = masked_token[0...AUTHENTICITY_TOKEN_LENGTH]
-
encrypted_csrf_token = masked_token[AUTHENTICITY_TOKEN_LENGTH..-1]
-
csrf_token = xor_byte_strings(one_time_pad, encrypted_csrf_token)
-
-
compare_with_real_token csrf_token, session
-
-
else
-
false # Token is malformed
-
end
-
end
-
-
1
def compare_with_real_token(token, session)
-
ActiveSupport::SecurityUtils.secure_compare(token, real_csrf_token(session))
-
end
-
-
1
def real_csrf_token(session)
-
session[:_csrf_token] ||= SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
-
Base64.strict_decode64(session[:_csrf_token])
-
end
-
-
1
def xor_byte_strings(s1, s2)
-
s1.bytes.zip(s2.bytes).map { |(c1,c2)| c1 ^ c2 }.pack('c*')
-
end
-
-
# The form's authenticity parameter. Override to provide your own.
-
1
def form_authenticity_param
-
params[request_forgery_protection_token]
-
end
-
-
# Checks if the controller allows forgery protection.
-
1
def protect_against_forgery?
-
16
allow_forgery_protection
-
end
-
end
-
end
-
1
module ActionController #:nodoc:
-
# This module is responsible to provide `rescue_from` helpers
-
# to controllers and configure when detailed exceptions must be
-
# shown.
-
1
module Rescue
-
1
extend ActiveSupport::Concern
-
1
include ActiveSupport::Rescuable
-
-
1
def rescue_with_handler(exception)
-
if (exception.respond_to?(:original_exception) &&
-
(orig_exception = exception.original_exception) &&
-
handler_for_rescue(orig_exception))
-
exception = orig_exception
-
end
-
super(exception)
-
end
-
-
# Override this method if you want to customize when detailed
-
# exceptions must be shown. This method is only called when
-
# consider_all_requests_local is false. By default, it returns
-
# false, but someone may set it to `request.local?` so local
-
# requests in production still shows the detailed exception pages.
-
1
def show_detailed_exceptions?
-
false
-
end
-
-
1
private
-
1
def process_action(*args)
-
16
super
-
rescue Exception => exception
-
request.env['action_dispatch.show_detailed_exceptions'] ||= show_detailed_exceptions?
-
rescue_with_handler(exception) || raise(exception)
-
end
-
end
-
end
-
1
require 'rack/chunked'
-
-
1
module ActionController #:nodoc:
-
# Allows views to be streamed back to the client as they are rendered.
-
#
-
# The default way Rails renders views is by first rendering the template
-
# and then the layout. The response is sent to the client after the whole
-
# template is rendered, all queries are made, and the layout is processed.
-
#
-
# Streaming inverts the rendering flow by rendering the layout first and
-
# streaming each part of the layout as they are processed. This allows the
-
# header of the HTML (which is usually in the layout) to be streamed back
-
# to client very quickly, allowing JavaScripts and stylesheets to be loaded
-
# earlier than usual.
-
#
-
# This approach was introduced in Rails 3.1 and is still improving. Several
-
# Rack middlewares may not work and you need to be careful when streaming.
-
# Those points are going to be addressed soon.
-
#
-
# In order to use streaming, you will need to use a Ruby version that
-
# supports fibers (fibers are supported since version 1.9.2 of the main
-
# Ruby implementation).
-
#
-
# Streaming can be added to a given template easily, all you need to do is
-
# to pass the :stream option.
-
#
-
# class PostsController
-
# def index
-
# @posts = Post.all
-
# render stream: true
-
# end
-
# end
-
#
-
# == When to use streaming
-
#
-
# Streaming may be considered to be overkill for lightweight actions like
-
# +new+ or +edit+. The real benefit of streaming is on expensive actions
-
# that, for example, do a lot of queries on the database.
-
#
-
# In such actions, you want to delay queries execution as much as you can.
-
# For example, imagine the following +dashboard+ action:
-
#
-
# def dashboard
-
# @posts = Post.all
-
# @pages = Page.all
-
# @articles = Article.all
-
# end
-
#
-
# Most of the queries here are happening in the controller. In order to benefit
-
# from streaming you would want to rewrite it as:
-
#
-
# def dashboard
-
# # Allow lazy execution of the queries
-
# @posts = Post.all
-
# @pages = Page.all
-
# @articles = Article.all
-
# render stream: true
-
# end
-
#
-
# Notice that :stream only works with templates. Rendering :json
-
# or :xml with :stream won't work.
-
#
-
# == Communication between layout and template
-
#
-
# When streaming, rendering happens top-down instead of inside-out.
-
# Rails starts with the layout, and the template is rendered later,
-
# when its +yield+ is reached.
-
#
-
# This means that, if your application currently relies on instance
-
# variables set in the template to be used in the layout, they won't
-
# work once you move to streaming. The proper way to communicate
-
# between layout and template, regardless of whether you use streaming
-
# or not, is by using +content_for+, +provide+ and +yield+.
-
#
-
# Take a simple example where the layout expects the template to tell
-
# which title to use:
-
#
-
# <html>
-
# <head><title><%= yield :title %></title></head>
-
# <body><%= yield %></body>
-
# </html>
-
#
-
# You would use +content_for+ in your template to specify the title:
-
#
-
# <%= content_for :title, "Main" %>
-
# Hello
-
#
-
# And the final result would be:
-
#
-
# <html>
-
# <head><title>Main</title></head>
-
# <body>Hello</body>
-
# </html>
-
#
-
# However, if +content_for+ is called several times, the final result
-
# would have all calls concatenated. For instance, if we have the following
-
# template:
-
#
-
# <%= content_for :title, "Main" %>
-
# Hello
-
# <%= content_for :title, " page" %>
-
#
-
# The final result would be:
-
#
-
# <html>
-
# <head><title>Main page</title></head>
-
# <body>Hello</body>
-
# </html>
-
#
-
# This means that, if you have <code>yield :title</code> in your layout
-
# and you want to use streaming, you would have to render the whole template
-
# (and eventually trigger all queries) before streaming the title and all
-
# assets, which kills the purpose of streaming. For this reason Rails 3.1
-
# introduces a new helper called +provide+ that does the same as +content_for+
-
# but tells the layout to stop searching for other entries and continue rendering.
-
#
-
# For instance, the template above using +provide+ would be:
-
#
-
# <%= provide :title, "Main" %>
-
# Hello
-
# <%= content_for :title, " page" %>
-
#
-
# Giving:
-
#
-
# <html>
-
# <head><title>Main</title></head>
-
# <body>Hello</body>
-
# </html>
-
#
-
# That said, when streaming, you need to properly check your templates
-
# and choose when to use +provide+ and +content_for+.
-
#
-
# == Headers, cookies, session and flash
-
#
-
# When streaming, the HTTP headers are sent to the client right before
-
# it renders the first line. This means that, modifying headers, cookies,
-
# session or flash after the template starts rendering will not propagate
-
# to the client.
-
#
-
# == Middlewares
-
#
-
# Middlewares that need to manipulate the body won't work with streaming.
-
# You should disable those middlewares whenever streaming in development
-
# or production. For instance, <tt>Rack::Bug</tt> won't work when streaming as it
-
# needs to inject contents in the HTML body.
-
#
-
# Also <tt>Rack::Cache</tt> won't work with streaming as it does not support
-
# streaming bodies yet. Whenever streaming Cache-Control is automatically
-
# set to "no-cache".
-
#
-
# == Errors
-
#
-
# When it comes to streaming, exceptions get a bit more complicated. This
-
# happens because part of the template was already rendered and streamed to
-
# the client, making it impossible to render a whole exception page.
-
#
-
# Currently, when an exception happens in development or production, Rails
-
# will automatically stream to the client:
-
#
-
# "><script>window.location = "/500.html"</script></html>
-
#
-
# The first two characters (">) are required in case the exception happens
-
# while rendering attributes for a given tag. You can check the real cause
-
# for the exception in your logger.
-
#
-
# == Web server support
-
#
-
# Not all web servers support streaming out-of-the-box. You need to check
-
# the instructions for each of them.
-
#
-
# ==== Unicorn
-
#
-
# Unicorn supports streaming but it needs to be configured. For this, you
-
# need to create a config file as follow:
-
#
-
# # unicorn.config.rb
-
# listen 3000, tcp_nopush: false
-
#
-
# And use it on initialization:
-
#
-
# unicorn_rails --config-file unicorn.config.rb
-
#
-
# You may also want to configure other parameters like <tt>:tcp_nodelay</tt>.
-
# Please check its documentation for more information: http://unicorn.bogomips.org/Unicorn/Configurator.html#method-i-listen
-
#
-
# If you are using Unicorn with NGINX, you may need to tweak NGINX.
-
# Streaming should work out of the box on Rainbows.
-
#
-
# ==== Passenger
-
#
-
# To be described.
-
#
-
1
module Streaming
-
1
extend ActiveSupport::Concern
-
-
1
protected
-
-
# Set proper cache control and transfer encoding when streaming
-
1
def _process_options(options) #:nodoc:
-
9
super
-
9
if options[:stream]
-
if env["HTTP_VERSION"] == "HTTP/1.0"
-
options.delete(:stream)
-
else
-
headers["Cache-Control"] ||= "no-cache"
-
headers["Transfer-Encoding"] = "chunked"
-
headers.delete("Content-Length")
-
end
-
end
-
end
-
-
# Call render_body if we are streaming instead of usual +render+.
-
1
def _render_template(options) #:nodoc:
-
9
if options.delete(:stream)
-
Rack::Chunked::Body.new view_renderer.render_body(view_context, options)
-
else
-
9
super
-
end
-
end
-
end
-
end
-
1
module ActionController
-
1
module Testing
-
1
extend ActiveSupport::Concern
-
-
1
include RackDelegation
-
-
# TODO : Rewrite tests using controller.headers= to use Rack env
-
1
def headers=(new_headers)
-
@_response ||= ActionDispatch::Response.new
-
@_response.headers.replace(new_headers)
-
end
-
-
# Behavior specific to functional tests
-
1
module Functional # :nodoc:
-
1
def set_response!(request)
-
end
-
-
1
def recycle!
-
32
@_url_options = nil
-
32
self.formats = nil
-
32
self.params = nil
-
end
-
end
-
-
1
module ClassMethods
-
1
def before_filters
-
_process_action_callbacks.find_all{|x| x.kind == :before}.map{|x| x.name}
-
end
-
end
-
end
-
end
-
1
module ActionController
-
# Includes +url_for+ into the host class. The class has to provide a +RouteSet+ by implementing
-
# the <tt>_routes</tt> method. Otherwise, an exception will be raised.
-
#
-
# In addition to <tt>AbstractController::UrlFor</tt>, this module accesses the HTTP layer to define
-
# url options like the +host+. In order to do so, this module requires the host class
-
# to implement +env+ and +request+, which need to be a Rack-compatible.
-
#
-
# class RootUrl
-
# include ActionController::UrlFor
-
# include Rails.application.routes.url_helpers
-
#
-
# delegate :env, :request, to: :controller
-
#
-
# def initialize(controller)
-
# @controller = controller
-
# @url = root_path # named route from the application.
-
# end
-
# end
-
1
module UrlFor
-
1
extend ActiveSupport::Concern
-
-
1
include AbstractController::UrlFor
-
-
1
def url_options
-
@_url_options ||= {
-
:host => request.host,
-
:port => request.optional_port,
-
:protocol => request.protocol,
-
:_recall => request.path_parameters
-
28
}.merge!(super).freeze
-
-
28
if (same_origin = _routes.equal?(env["action_dispatch.routes".freeze])) ||
-
56
(script_name = env["ROUTES_#{_routes.object_id}_SCRIPT_NAME"]) ||
-
28
(original_script_name = env['ORIGINAL_SCRIPT_NAME'.freeze])
-
-
options = @_url_options.dup
-
if original_script_name
-
options[:original_script_name] = original_script_name
-
else
-
options[:script_name] = same_origin ? request.script_name.dup : script_name
-
end
-
options.freeze
-
else
-
28
@_url_options
-
end
-
end
-
end
-
end
-
1
require 'rack/session/abstract/id'
-
1
require 'active_support/core_ext/object/to_query'
-
1
require 'active_support/core_ext/module/anonymous'
-
1
require 'active_support/core_ext/hash/keys'
-
1
require 'active_support/deprecation'
-
-
1
require 'rails-dom-testing'
-
-
1
module ActionController
-
1
module TemplateAssertions
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
7
setup :setup_subscriptions
-
7
teardown :teardown_subscriptions
-
end
-
-
1
RENDER_TEMPLATE_INSTANCE_VARIABLES = %w{partials templates layouts files}.freeze
-
-
1
def setup_subscriptions
-
18
RENDER_TEMPLATE_INSTANCE_VARIABLES.each do |instance_variable|
-
72
instance_variable_set("@_#{instance_variable}", Hash.new(0))
-
end
-
-
18
@_subscribers = []
-
-
@_subscribers << ActiveSupport::Notifications.subscribe("render_template.action_view") do |_name, _start, _finish, _id, payload|
-
9
path = payload[:layout]
-
9
if path
-
9
@_layouts[path] += 1
-
9
if path =~ /^layouts\/(.*)/
-
9
@_layouts[$1] += 1
-
end
-
end
-
18
end
-
-
@_subscribers << ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload|
-
18
if virtual_path = payload[:virtual_path]
-
18
partial = virtual_path =~ /^.*\/_[^\/]*$/
-
-
18
if partial
-
@_partials[virtual_path] += 1
-
@_partials[virtual_path.split("/").last] += 1
-
end
-
-
18
@_templates[virtual_path] += 1
-
else
-
path = payload[:identifier]
-
if path
-
@_files[path] += 1
-
@_files[path.split("/").last] += 1
-
end
-
end
-
18
end
-
end
-
-
1
def teardown_subscriptions
-
18
return unless defined?(@_subscribers)
-
-
18
@_subscribers.each do |subscriber|
-
36
ActiveSupport::Notifications.unsubscribe(subscriber)
-
end
-
end
-
-
1
def process(*args)
-
16
reset_template_assertion
-
16
super
-
end
-
-
1
def reset_template_assertion
-
16
RENDER_TEMPLATE_INSTANCE_VARIABLES.each do |instance_variable|
-
64
ivar_name = "@_#{instance_variable}"
-
64
if instance_variable_defined?(ivar_name)
-
64
instance_variable_get(ivar_name).clear
-
end
-
end
-
end
-
-
# Asserts that the request was rendered with the appropriate template file or partials.
-
#
-
# # assert that the "new" view template was rendered
-
# assert_template "new"
-
#
-
# # assert that the exact template "admin/posts/new" was rendered
-
# assert_template %r{\Aadmin/posts/new\Z}
-
#
-
# # assert that the layout 'admin' was rendered
-
# assert_template layout: 'admin'
-
# assert_template layout: 'layouts/admin'
-
# assert_template layout: :admin
-
#
-
# # assert that no layout was rendered
-
# assert_template layout: nil
-
# assert_template layout: false
-
#
-
# # assert that the "_customer" partial was rendered twice
-
# assert_template partial: '_customer', count: 2
-
#
-
# # assert that no partials were rendered
-
# assert_template partial: false
-
#
-
# # assert that a file was rendered
-
# assert_template file: "README.rdoc"
-
#
-
# # assert that no file was rendered
-
# assert_template file: nil
-
# assert_template file: false
-
#
-
# In a view test case, you can also assert that specific locals are passed
-
# to partials:
-
#
-
# # assert that the "_customer" partial was rendered with a specific object
-
# assert_template partial: '_customer', locals: { customer: @customer }
-
1
def assert_template(options = {}, message = nil)
-
# Force body to be read in case the template is being streamed.
-
1
response.body
-
-
1
case options
-
when NilClass, Regexp, String, Symbol
-
1
options = options.to_s if Symbol === options
-
1
rendered = @_templates
-
1
msg = message || sprintf("expecting <%s> but rendering with <%s>",
-
options.inspect, rendered.keys)
-
1
matches_template =
-
case options
-
when String
-
!options.empty? && rendered.any? do |t, num|
-
1
options_splited = options.split(File::SEPARATOR)
-
1
t_splited = t.split(File::SEPARATOR)
-
1
t_splited.last(options_splited.size) == options_splited
-
1
end
-
when Regexp
-
rendered.any? { |t,num| t.match(options) }
-
when NilClass
-
rendered.blank?
-
end
-
1
assert matches_template, msg
-
when Hash
-
options.assert_valid_keys(:layout, :partial, :locals, :count, :file)
-
-
if options.key?(:layout)
-
expected_layout = options[:layout]
-
msg = message || sprintf("expecting layout <%s> but action rendered <%s>",
-
expected_layout, @_layouts.keys)
-
-
case expected_layout
-
when String, Symbol
-
assert_includes @_layouts.keys, expected_layout.to_s, msg
-
when Regexp
-
assert(@_layouts.keys.any? {|l| l =~ expected_layout }, msg)
-
when nil, false
-
assert(@_layouts.empty?, msg)
-
end
-
end
-
-
if options[:file]
-
assert_includes @_files.keys, options[:file]
-
elsif options.key?(:file)
-
assert @_files.blank?, "expected no files but #{@_files.keys} was rendered"
-
end
-
-
if expected_partial = options[:partial]
-
if expected_locals = options[:locals]
-
if defined?(@_rendered_views)
-
view = expected_partial.to_s.sub(/^_/, '').sub(/\/_(?=[^\/]+\z)/, '/')
-
-
partial_was_not_rendered_msg = "expected %s to be rendered but it was not." % view
-
assert_includes @_rendered_views.rendered_views, view, partial_was_not_rendered_msg
-
-
msg = 'expecting %s to be rendered with %s but was with %s' % [expected_partial,
-
expected_locals,
-
@_rendered_views.locals_for(view)]
-
assert(@_rendered_views.view_rendered?(view, options[:locals]), msg)
-
else
-
warn "the :locals option to #assert_template is only supported in a ActionView::TestCase"
-
end
-
elsif expected_count = options[:count]
-
actual_count = @_partials[expected_partial]
-
msg = message || sprintf("expecting %s to be rendered %s time(s) but rendered %s time(s)",
-
expected_partial, expected_count, actual_count)
-
assert(actual_count == expected_count.to_i, msg)
-
else
-
msg = message || sprintf("expecting partial <%s> but action rendered <%s>",
-
options[:partial], @_partials.keys)
-
assert_includes @_partials, expected_partial, msg
-
end
-
elsif options.key?(:partial)
-
assert @_partials.empty?,
-
"Expected no partials to be rendered"
-
end
-
else
-
raise ArgumentError, "assert_template only accepts a String, Symbol, Hash, Regexp, or nil"
-
end
-
end
-
end
-
-
1
class TestRequest < ActionDispatch::TestRequest #:nodoc:
-
1
DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup
-
1
DEFAULT_ENV.delete 'PATH_INFO'
-
-
1
def initialize(env = {})
-
18
super
-
-
18
self.session = TestSession.new
-
18
self.session_options = TestSession::DEFAULT_OPTIONS.merge(:id => SecureRandom.hex(16))
-
end
-
-
1
def assign_parameters(routes, controller_path, action, parameters = {})
-
16
parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action)
-
16
extra_keys = routes.extra_keys(parameters)
-
16
non_path_parameters = get? ? query_parameters : request_parameters
-
16
parameters.each do |key, value|
-
53
if value.is_a?(Array) && (value.frozen? || value.any?(&:frozen?))
-
value = value.map{ |v| v.duplicable? ? v.dup : v }
-
55
elsif value.is_a?(Hash) && (value.frozen? || value.any?{ |k,v| v.frozen? })
-
14
value = Hash[value.map{ |k,v| [k, v.duplicable? ? v.dup : v] }]
-
elsif value.frozen? && value.duplicable?
-
value = value.dup
-
end
-
-
53
if extra_keys.include?(key)
-
13
non_path_parameters[key] = value
-
else
-
40
if value.is_a?(Array)
-
value = value.map(&:to_param)
-
else
-
40
value = value.to_param
-
end
-
-
40
path_parameters[key] = value
-
end
-
end
-
-
# Clear the combined params hash in case it was already referenced.
-
16
@env.delete("action_dispatch.request.parameters")
-
-
# Clear the filter cache variables so they're not stale
-
16
@filtered_parameters = @filtered_env = @filtered_path = nil
-
-
16
params = self.request_parameters.dup
-
16
%w(controller action only_path).each do |k|
-
48
params.delete(k)
-
48
params.delete(k.to_sym)
-
end
-
16
data = params.to_query
-
-
16
@env['CONTENT_LENGTH'] = data.length.to_s
-
16
@env['rack.input'] = StringIO.new(data)
-
end
-
-
1
def recycle!
-
16
@formats = nil
-
576
@env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
-
576
@env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
-
16
@method = @request_method = nil
-
16
@fullpath = @ip = @remote_ip = @protocol = nil
-
16
@env['action_dispatch.request.query_parameters'] = {}
-
16
@set_cookies ||= {}
-
16
@set_cookies.update(Hash[cookie_jar.instance_variable_get("@set_cookies").map{ |k,o| [k,o[:value]] }])
-
16
deleted_cookies = cookie_jar.instance_variable_get("@delete_cookies")
-
16
@set_cookies.reject!{ |k,v| deleted_cookies.include?(k) }
-
16
cookie_jar.update(rack_cookies)
-
16
cookie_jar.update(cookies)
-
16
cookie_jar.update(@set_cookies)
-
16
cookie_jar.recycle!
-
end
-
-
1
private
-
-
1
def default_env
-
18
DEFAULT_ENV
-
end
-
end
-
-
1
class TestResponse < ActionDispatch::TestResponse
-
1
def recycle!
-
16
initialize
-
end
-
end
-
-
1
class LiveTestResponse < Live::Response
-
1
def recycle!
-
@body = nil
-
initialize
-
end
-
-
1
def body
-
@body ||= super
-
end
-
-
# Was the response successful?
-
1
alias_method :success?, :successful?
-
-
# Was the URL not found?
-
1
alias_method :missing?, :not_found?
-
-
# Were we redirected?
-
1
alias_method :redirect?, :redirection?
-
-
# Was there a server-side error?
-
1
alias_method :error?, :server_error?
-
end
-
-
# Methods #destroy and #load! are overridden to avoid calling methods on the
-
# @store object, which does not exist for the TestSession class.
-
1
class TestSession < Rack::Session::Abstract::SessionHash #:nodoc:
-
1
DEFAULT_OPTIONS = Rack::Session::Abstract::ID::DEFAULT_OPTIONS
-
-
1
def initialize(session = {})
-
18
super(nil, nil)
-
18
@id = SecureRandom.hex(16)
-
18
@data = stringify_keys(session)
-
18
@loaded = true
-
end
-
-
1
def exists?
-
true
-
end
-
-
1
def keys
-
@data.keys
-
end
-
-
1
def values
-
@data.values
-
end
-
-
1
def destroy
-
clear
-
end
-
-
1
def fetch(key, *args, &block)
-
@data.fetch(key.to_s, *args, &block)
-
end
-
-
1
private
-
-
1
def load!
-
@id
-
end
-
end
-
-
# Superclass for ActionController functional tests. Functional tests allow you to
-
# test a single controller action per test method. This should not be confused with
-
# integration tests (see ActionDispatch::IntegrationTest), which are more like
-
# "stories" that can involve multiple controllers and multiple actions (i.e. multiple
-
# different HTTP requests).
-
#
-
# == Basic example
-
#
-
# Functional tests are written as follows:
-
# 1. First, one uses the +get+, +post+, +patch+, +put+, +delete+ or +head+ method to simulate
-
# an HTTP request.
-
# 2. Then, one asserts whether the current state is as expected. "State" can be anything:
-
# the controller's HTTP response, the database contents, etc.
-
#
-
# For example:
-
#
-
# class BooksControllerTest < ActionController::TestCase
-
# def test_create
-
# # Simulate a POST response with the given HTTP parameters.
-
# post(:create, book: { title: "Love Hina" })
-
#
-
# # Assert that the controller tried to redirect us to
-
# # the created book's URI.
-
# assert_response :found
-
#
-
# # Assert that the controller really put the book in the database.
-
# assert_not_nil Book.find_by(title: "Love Hina")
-
# end
-
# end
-
#
-
# You can also send a real document in the simulated HTTP request.
-
#
-
# def test_create
-
# json = {book: { title: "Love Hina" }}.to_json
-
# post :create, json
-
# end
-
#
-
# == Special instance variables
-
#
-
# ActionController::TestCase will also automatically provide the following instance
-
# variables for use in the tests:
-
#
-
# <b>@controller</b>::
-
# The controller instance that will be tested.
-
# <b>@request</b>::
-
# An ActionController::TestRequest, representing the current HTTP
-
# request. You can modify this object before sending the HTTP request. For example,
-
# you might want to set some session properties before sending a GET request.
-
# <b>@response</b>::
-
# An ActionController::TestResponse object, representing the response
-
# of the last HTTP response. In the above example, <tt>@response</tt> becomes valid
-
# after calling +post+. If the various assert methods are not sufficient, then you
-
# may use this object to inspect the HTTP response in detail.
-
#
-
# (Earlier versions of \Rails required each functional test to subclass
-
# Test::Unit::TestCase and define @controller, @request, @response in +setup+.)
-
#
-
# == Controller is automatically inferred
-
#
-
# ActionController::TestCase will automatically infer the controller under test
-
# from the test class name. If the controller cannot be inferred from the test
-
# class name, you can explicitly set it with +tests+.
-
#
-
# class SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase
-
# tests WidgetController
-
# end
-
#
-
# == \Testing controller internals
-
#
-
# In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions
-
# can be used against. These collections are:
-
#
-
# * assigns: Instance variables assigned in the action that are available for the view.
-
# * session: Objects being saved in the session.
-
# * flash: The flash objects currently in the session.
-
# * cookies: \Cookies being sent to the user on this request.
-
#
-
# These collections can be used just like any other hash:
-
#
-
# assert_not_nil assigns(:person) # makes sure that a @person instance variable was set
-
# assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
-
# assert flash.empty? # makes sure that there's nothing in the flash
-
#
-
# For historic reasons, the assigns hash uses string-based keys. So <tt>assigns[:person]</tt> won't work, but <tt>assigns["person"]</tt> will. To
-
# appease our yearning for symbols, though, an alternative accessor has been devised using a method call instead of index referencing.
-
# So <tt>assigns(:person)</tt> will work just like <tt>assigns["person"]</tt>, but again, <tt>assigns[:person]</tt> will not work.
-
#
-
# On top of the collections, you have the complete url that a given action redirected to available in <tt>redirect_to_url</tt>.
-
#
-
# For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
-
# action call which can then be asserted against.
-
#
-
# == Manipulating session and cookie variables
-
#
-
# Sometimes you need to set up the session and cookie variables for a test.
-
# To do this just assign a value to the session or cookie collection:
-
#
-
# session[:key] = "value"
-
# cookies[:key] = "value"
-
#
-
# To clear the cookies for a test just clear the cookie collection:
-
#
-
# cookies.clear
-
#
-
# == \Testing named routes
-
#
-
# If you're using named routes, they can be easily tested using the original named routes' methods straight in the test case.
-
#
-
# assert_redirected_to page_url(title: 'foo')
-
1
class TestCase < ActiveSupport::TestCase
-
1
module Behavior
-
1
extend ActiveSupport::Concern
-
1
include ActionDispatch::TestProcess
-
1
include ActiveSupport::Testing::ConstantLookup
-
1
include Rails::Dom::Testing::Assertions
-
-
1
attr_reader :response, :request
-
-
1
module ClassMethods
-
-
# Sets the controller class name. Useful if the name can't be inferred from test class.
-
# Normalizes +controller_class+ before using.
-
#
-
# tests WidgetController
-
# tests :widget
-
# tests 'widget'
-
1
def tests(controller_class)
-
case controller_class
-
when String, Symbol
-
self.controller_class = "#{controller_class.to_s.camelize}Controller".constantize
-
when Class
-
self.controller_class = controller_class
-
else
-
raise ArgumentError, "controller class must be a String, Symbol, or Class"
-
end
-
end
-
-
1
def controller_class=(new_class)
-
self._controller_class = new_class
-
end
-
-
1
def controller_class
-
if current_controller_class = self._controller_class
-
current_controller_class
-
else
-
self.controller_class = determine_default_controller_class(name)
-
end
-
end
-
-
1
def determine_default_controller_class(name)
-
determine_constant_from_test_name(name) do |constant|
-
Class === constant && constant < ActionController::Metal
-
end
-
end
-
end
-
-
# Simulate a GET request with the given parameters.
-
#
-
# - +action+: The controller action to call.
-
# - +parameters+: The HTTP parameters that you want to pass. This may
-
# be +nil+, a hash, or a string that is appropriately encoded
-
# (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
-
# - +session+: A hash of parameters to store in the session. This may be +nil+.
-
# - +flash+: A hash of parameters to store in the flash. This may be +nil+.
-
#
-
# You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with
-
# +post+, +patch+, +put+, +delete+, and +head+.
-
#
-
# Note that the request method is not verified. The different methods are
-
# available to make the tests more expressive.
-
1
def get(action, *args)
-
9
process(action, "GET", *args)
-
end
-
-
# Simulate a POST request with the given parameters and set/volley the response.
-
# See +get+ for more details.
-
1
def post(action, *args)
-
5
process(action, "POST", *args)
-
end
-
-
# Simulate a PATCH request with the given parameters and set/volley the response.
-
# See +get+ for more details.
-
1
def patch(action, *args)
-
process(action, "PATCH", *args)
-
end
-
-
# Simulate a PUT request with the given parameters and set/volley the response.
-
# See +get+ for more details.
-
1
def put(action, *args)
-
2
process(action, "PUT", *args)
-
end
-
-
# Simulate a DELETE request with the given parameters and set/volley the response.
-
# See +get+ for more details.
-
1
def delete(action, *args)
-
process(action, "DELETE", *args)
-
end
-
-
# Simulate a HEAD request with the given parameters and set/volley the response.
-
# See +get+ for more details.
-
1
def head(action, *args)
-
process(action, "HEAD", *args)
-
end
-
-
1
def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
-
@request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
-
@request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
-
__send__(request_method, action, parameters, session, flash).tap do
-
@request.env.delete 'HTTP_X_REQUESTED_WITH'
-
@request.env.delete 'HTTP_ACCEPT'
-
end
-
end
-
1
alias xhr :xml_http_request
-
-
1
def paramify_values(hash_or_array_or_value)
-
92
case hash_or_array_or_value
-
when Hash
-
104
Hash[hash_or_array_or_value.map{|key, value| [key, paramify_values(value)] }]
-
when Array
-
hash_or_array_or_value.map {|i| paramify_values(i)}
-
when Rack::Test::UploadedFile, ActionDispatch::Http::UploadedFile
-
hash_or_array_or_value
-
else
-
64
hash_or_array_or_value.to_param
-
end
-
end
-
-
# Simulate a HTTP request to +action+ by specifying request method,
-
# parameters and set/volley the response.
-
#
-
# - +action+: The controller action to call.
-
# - +http_method+: Request method used to send the http request. Possible values
-
# are +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, +HEAD+. Defaults to +GET+.
-
# - +parameters+: The HTTP parameters. This may be +nil+, a hash, or a
-
# string that is appropriately encoded (+application/x-www-form-urlencoded+
-
# or +multipart/form-data+).
-
# - +session+: A hash of parameters to store in the session. This may be +nil+.
-
# - +flash+: A hash of parameters to store in the flash. This may be +nil+.
-
#
-
# Example calling +create+ action and sending two params:
-
#
-
# process :create, 'POST', user: { name: 'Gaurish Sharma', email: 'user@example.com' }
-
#
-
# Example sending parameters, +nil+ session and setting a flash message:
-
#
-
# process :view, 'GET', { id: 7 }, nil, { notice: 'This is flash message' }
-
#
-
# To simulate +GET+, +POST+, +PATCH+, +PUT+, +DELETE+ and +HEAD+ requests
-
# prefer using #get, #post, #patch, #put, #delete and #head methods
-
# respectively which will make tests more expressive.
-
#
-
# Note that the request method is not verified.
-
1
def process(action, http_method = 'GET', *args)
-
16
check_required_ivars
-
-
16
if args.first.is_a?(String) && http_method != 'HEAD'
-
@request.env['RAW_POST_DATA'] = args.shift
-
end
-
-
16
parameters, session, flash = args
-
16
parameters ||= {}
-
-
# Ensure that numbers and symbols passed as params are converted to
-
# proper params, as is the case when engaging rack.
-
16
parameters = paramify_values(parameters) if html_format?(parameters)
-
-
16
@html_document = nil
-
16
@html_scanner_document = nil
-
-
16
unless @controller.respond_to?(:recycle!)
-
16
@controller.extend(Testing::Functional)
-
end
-
-
16
@request.recycle!
-
16
@response.recycle!
-
16
@controller.recycle!
-
-
16
@request.env['REQUEST_METHOD'] = http_method
-
-
16
controller_class_name = @controller.class.anonymous? ?
-
"anonymous" :
-
@controller.class.controller_path
-
-
16
@request.assign_parameters(@routes, controller_class_name, action.to_s, parameters)
-
-
16
@request.session.update(session) if session
-
16
@request.flash.update(flash || {})
-
-
16
@controller.request = @request
-
16
@controller.response = @response
-
-
16
build_request_uri(action, parameters)
-
-
16
name = @request.parameters[:action]
-
-
16
@controller.recycle!
-
16
@controller.process(name)
-
-
16
if cookies = @request.env['action_dispatch.cookies']
-
16
unless @response.committed?
-
16
cookies.write(@response)
-
end
-
end
-
16
@response.prepare!
-
-
16
@assigns = @controller.respond_to?(:view_assigns) ? @controller.view_assigns : {}
-
-
16
if flash_value = @request.flash.to_session_value
-
5
@request.session['flash'] = flash_value
-
end
-
-
16
@response
-
end
-
-
1
def setup_controller_request_and_response
-
18
@controller = nil unless defined? @controller
-
-
18
response_klass = TestResponse
-
-
18
if klass = self.class.controller_class
-
18
if klass < ActionController::Live
-
response_klass = LiveTestResponse
-
end
-
18
unless @controller
-
18
begin
-
18
@controller = klass.new
-
rescue
-
warn "could not construct controller #{klass}" if $VERBOSE
-
end
-
end
-
end
-
-
18
@request = build_request
-
18
@response = build_response response_klass
-
18
@response.request = @request
-
-
18
if @controller
-
18
@controller.request = @request
-
18
@controller.params = {}
-
end
-
end
-
-
1
def build_request
-
18
TestRequest.new
-
end
-
-
1
def build_response(klass)
-
18
klass.new
-
end
-
-
1
included do
-
5
include ActionController::TemplateAssertions
-
5
include ActionDispatch::Assertions
-
5
class_attribute :_controller_class
-
5
setup :setup_controller_request_and_response
-
end
-
-
1
private
-
-
1
def document_root_element
-
html_document.root
-
end
-
-
1
def check_required_ivars
-
# Sanity check for required instance variables so we can give an
-
# understandable error message.
-
16
[:@routes, :@controller, :@request, :@response].each do |iv_name|
-
64
if !instance_variable_defined?(iv_name) || instance_variable_get(iv_name).nil?
-
raise "#{iv_name} is nil: make sure you set it in your test's setup method."
-
end
-
end
-
end
-
-
1
def build_request_uri(action, parameters)
-
16
unless @request.env["PATH_INFO"]
-
16
options = @controller.respond_to?(:url_options) ? @controller.__send__(:url_options).merge(parameters) : parameters
-
16
options.update(
-
:action => action,
-
:relative_url_root => nil,
-
:_recall => @request.path_parameters)
-
-
16
if route_name = options.delete(:use_route)
-
ActiveSupport::Deprecation.warn <<-MSG.squish
-
Passing the `use_route` option in functional tests are deprecated.
-
Support for this option in the `process` method (and the related
-
`get`, `head`, `post`, `patch`, `put` and `delete` helpers) will
-
be removed in the next version without replacement.
-
-
Functional tests are essentially unit tests for controllers and
-
they should not require knowledge to how the application's routes
-
are configured. Instead, you should explicitly pass the appropiate
-
params to the `process` method.
-
-
Previously the engines guide also contained an incorrect example
-
that recommended using this option to test an engine's controllers
-
within the dummy application. That recommendation was incorrect
-
and has since been corrected. Instead, you should override the
-
`@routes` variable in the test case with `Foo::Engine.routes`. See
-
the updated engines guide for details.
-
MSG
-
end
-
-
16
url, query_string = @routes.path_for(options, route_name).split("?", 2)
-
-
16
@request.env["SCRIPT_NAME"] = @controller.config.relative_url_root
-
16
@request.env["PATH_INFO"] = url
-
16
@request.env["QUERY_STRING"] = query_string || ""
-
end
-
end
-
-
1
def html_format?(parameters)
-
16
return true unless parameters.key?(:format)
-
Mime.fetch(parameters[:format]) { Mime['html'] }.html?
-
end
-
end
-
-
1
include Behavior
-
end
-
end
-
1
require 'rails-dom-testing'
-
-
1
module ActionDispatch
-
1
module Assertions
-
1
autoload :ResponseAssertions, 'action_dispatch/testing/assertions/response'
-
1
autoload :RoutingAssertions, 'action_dispatch/testing/assertions/routing'
-
-
1
extend ActiveSupport::Concern
-
-
1
include ResponseAssertions
-
1
include RoutingAssertions
-
1
include Rails::Dom::Testing::Assertions
-
-
1
def html_document
-
@html_document ||= if @response.content_type.to_s =~ /xml$/
-
Nokogiri::XML::Document.parse(@response.body)
-
else
-
Nokogiri::HTML::Document.parse(@response.body)
-
end
-
end
-
end
-
end
-
-
1
module ActionDispatch
-
1
module Assertions
-
# A small suite of assertions that test responses from \Rails applications.
-
1
module ResponseAssertions
-
# Asserts that the response is one of the following types:
-
#
-
# * <tt>:success</tt> - Status code was in the 200-299 range
-
# * <tt>:redirect</tt> - Status code was in the 300-399 range
-
# * <tt>:missing</tt> - Status code was 404
-
# * <tt>:error</tt> - Status code was in the 500-599 range
-
#
-
# You can also pass an explicit status number like <tt>assert_response(501)</tt>
-
# or its symbolic equivalent <tt>assert_response(:not_implemented)</tt>.
-
# See Rack::Utils::SYMBOL_TO_STATUS_CODE for a full list.
-
#
-
# # assert that the response was a redirection
-
# assert_response :redirect
-
#
-
# # assert that the response code was status code 401 (unauthorized)
-
# assert_response 401
-
1
def assert_response(type, message = nil)
-
5
message ||= "Expected response to be a <#{type}>, but was <#{@response.response_code}>"
-
-
5
if Symbol === type
-
5
if [:success, :missing, :redirect, :error].include?(type)
-
5
assert @response.send("#{type}?"), message
-
else
-
code = Rack::Utils::SYMBOL_TO_STATUS_CODE[type]
-
if code.nil?
-
raise ArgumentError, "Invalid response type :#{type}"
-
end
-
assert_equal code, @response.response_code, message
-
end
-
else
-
assert_equal type, @response.response_code, message
-
end
-
end
-
-
# Assert that the redirection options passed in match those of the redirect called in the latest action.
-
# This match can be partial, such that <tt>assert_redirected_to(controller: "weblog")</tt> will also
-
# match the redirection of <tt>redirect_to(controller: "weblog", action: "show")</tt> and so on.
-
#
-
# # assert that the redirection was to the "index" action on the WeblogController
-
# assert_redirected_to controller: "weblog", action: "index"
-
#
-
# # assert that the redirection was to the named route login_url
-
# assert_redirected_to login_url
-
#
-
# # assert that the redirection was to the url for @customer
-
# assert_redirected_to @customer
-
#
-
# # asserts that the redirection matches the regular expression
-
# assert_redirected_to %r(\Ahttp://example.org)
-
1
def assert_redirected_to(options = {}, message=nil)
-
5
assert_response(:redirect, message)
-
5
return true if options === @response.location
-
-
2
redirect_is = normalize_argument_to_redirection(@response.location)
-
2
redirect_expected = normalize_argument_to_redirection(options)
-
-
2
message ||= "Expected response to be a redirect to <#{redirect_expected}> but was a redirect to <#{redirect_is}>"
-
2
assert_operator redirect_expected, :===, redirect_is, message
-
end
-
-
1
private
-
# Proxy to to_param if the object will respond to it.
-
1
def parameterize(value)
-
value.respond_to?(:to_param) ? value.to_param : value
-
end
-
-
1
def normalize_argument_to_redirection(fragment)
-
4
if Regexp === fragment
-
fragment
-
else
-
4
handle = @controller || ActionController::Redirecting
-
4
handle._compute_redirect_to_location(@request, fragment)
-
end
-
end
-
end
-
end
-
end
-
1
require 'uri'
-
1
require 'active_support/core_ext/hash/indifferent_access'
-
1
require 'active_support/core_ext/string/access'
-
1
require 'action_controller/metal/exceptions'
-
-
1
module ActionDispatch
-
1
module Assertions
-
# Suite of assertions to test routes generated by \Rails and the handling of requests made to them.
-
1
module RoutingAssertions
-
# Asserts that the routing of the given +path+ was handled correctly and that the parsed options (given in the +expected_options+ hash)
-
# match +path+. Basically, it asserts that \Rails recognizes the route given by +expected_options+.
-
#
-
# Pass a hash in the second argument (+path+) to specify the request method. This is useful for routes
-
# requiring a specific HTTP method. The hash should contain a :path with the incoming request path
-
# and a :method containing the required HTTP verb.
-
#
-
# # assert that POSTing to /items will call the create action on ItemsController
-
# assert_recognizes({controller: 'items', action: 'create'}, {path: 'items', method: :post})
-
#
-
# You can also pass in +extras+ with a hash containing URL parameters that would normally be in the query string. This can be used
-
# to assert that values in the query string string will end up in the params hash correctly. To test query strings you must use the
-
# extras argument, appending the query string on the path directly will not work. For example:
-
#
-
# # assert that a path of '/items/list/1?view=print' returns the correct options
-
# assert_recognizes({controller: 'items', action: 'list', id: '1', view: 'print'}, 'items/list/1', { view: "print" })
-
#
-
# The +message+ parameter allows you to pass in an error message that is displayed upon failure.
-
#
-
# # Check the default route (i.e., the index action)
-
# assert_recognizes({controller: 'items', action: 'index'}, 'items')
-
#
-
# # Test a specific action
-
# assert_recognizes({controller: 'items', action: 'list'}, 'items/list')
-
#
-
# # Test an action with a parameter
-
# assert_recognizes({controller: 'items', action: 'destroy', id: '1'}, 'items/destroy/1')
-
#
-
# # Test a custom route
-
# assert_recognizes({controller: 'items', action: 'show', id: '1'}, 'view/item1')
-
1
def assert_recognizes(expected_options, path, extras={}, msg=nil)
-
if path.is_a?(Hash) && path[:method].to_s == "all"
-
[:get, :post, :put, :delete].each do |method|
-
assert_recognizes(expected_options, path.merge(method: method), extras, msg)
-
end
-
else
-
request = recognized_request_for(path, extras, msg)
-
-
expected_options = expected_options.clone
-
-
expected_options.stringify_keys!
-
-
msg = message(msg, "") {
-
sprintf("The recognized options <%s> did not match <%s>, difference:",
-
request.path_parameters, expected_options)
-
}
-
-
assert_equal(expected_options, request.path_parameters, msg)
-
end
-
end
-
-
# Asserts that the provided options can be used to generate the provided path. This is the inverse of +assert_recognizes+.
-
# The +extras+ parameter is used to tell the request the names and values of additional request parameters that would be in
-
# a query string. The +message+ parameter allows you to specify a custom error message for assertion failures.
-
#
-
# The +defaults+ parameter is unused.
-
#
-
# # Asserts that the default action is generated for a route with no action
-
# assert_generates "/items", controller: "items", action: "index"
-
#
-
# # Tests that the list action is properly routed
-
# assert_generates "/items/list", controller: "items", action: "list"
-
#
-
# # Tests the generation of a route with a parameter
-
# assert_generates "/items/list/1", { controller: "items", action: "list", id: "1" }
-
#
-
# # Asserts that the generated route gives us our custom route
-
# assert_generates "changesets/12", { controller: 'scm', action: 'show_diff', revision: "12" }
-
1
def assert_generates(expected_path, options, defaults={}, extras={}, message=nil)
-
if expected_path =~ %r{://}
-
fail_on(URI::InvalidURIError, message) do
-
uri = URI.parse(expected_path)
-
expected_path = uri.path.to_s.empty? ? "/" : uri.path
-
end
-
else
-
expected_path = "/#{expected_path}" unless expected_path.first == '/'
-
end
-
# Load routes.rb if it hasn't been loaded.
-
-
generated_path, extra_keys = @routes.generate_extras(options, defaults)
-
found_extras = options.reject { |k, _| ! extra_keys.include? k }
-
-
msg = message || sprintf("found extras <%s>, not <%s>", found_extras, extras)
-
assert_equal(extras, found_extras, msg)
-
-
msg = message || sprintf("The generated path <%s> did not match <%s>", generated_path,
-
expected_path)
-
assert_equal(expected_path, generated_path, msg)
-
end
-
-
# Asserts that path and options match both ways; in other words, it verifies that <tt>path</tt> generates
-
# <tt>options</tt> and then that <tt>options</tt> generates <tt>path</tt>. This essentially combines +assert_recognizes+
-
# and +assert_generates+ into one step.
-
#
-
# The +extras+ hash allows you to specify options that would normally be provided as a query string to the action. The
-
# +message+ parameter allows you to specify a custom error message to display upon failure.
-
#
-
# # Assert a basic route: a controller with the default action (index)
-
# assert_routing '/home', controller: 'home', action: 'index'
-
#
-
# # Test a route generated with a specific controller, action, and parameter (id)
-
# assert_routing '/entries/show/23', controller: 'entries', action: 'show', id: 23
-
#
-
# # Assert a basic route (controller + default action), with an error message if it fails
-
# assert_routing '/store', { controller: 'store', action: 'index' }, {}, {}, 'Route for store index not generated properly'
-
#
-
# # Tests a route, providing a defaults hash
-
# assert_routing 'controller/action/9', {id: "9", item: "square"}, {controller: "controller", action: "action"}, {}, {item: "square"}
-
#
-
# # Tests a route with a HTTP method
-
# assert_routing({ method: 'put', path: '/product/321' }, { controller: "product", action: "update", id: "321" })
-
1
def assert_routing(path, options, defaults={}, extras={}, message=nil)
-
assert_recognizes(options, path, extras, message)
-
-
controller, default_controller = options[:controller], defaults[:controller]
-
if controller && controller.include?(?/) && default_controller && default_controller.include?(?/)
-
options[:controller] = "/#{controller}"
-
end
-
-
generate_options = options.dup.delete_if{ |k, _| defaults.key?(k) }
-
assert_generates(path.is_a?(Hash) ? path[:path] : path, generate_options, defaults, extras, message)
-
end
-
-
# A helper to make it easier to test different route configurations.
-
# This method temporarily replaces @routes
-
# with a new RouteSet instance.
-
#
-
# The new instance is yielded to the passed block. Typically the block
-
# will create some routes using <tt>set.draw { match ... }</tt>:
-
#
-
# with_routing do |set|
-
# set.draw do
-
# resources :users
-
# end
-
# assert_equal "/users", users_path
-
# end
-
#
-
1
def with_routing
-
old_routes, @routes = @routes, ActionDispatch::Routing::RouteSet.new
-
if defined?(@controller) && @controller
-
old_controller, @controller = @controller, @controller.clone
-
_routes = @routes
-
-
@controller.singleton_class.send(:include, _routes.url_helpers)
-
@controller.view_context_class = Class.new(@controller.view_context_class) do
-
include _routes.url_helpers
-
end
-
end
-
yield @routes
-
ensure
-
@routes = old_routes
-
if defined?(@controller) && @controller
-
@controller = old_controller
-
end
-
end
-
-
# ROUTES TODO: These assertions should really work in an integration context
-
1
def method_missing(selector, *args, &block)
-
1
if defined?(@controller) && @controller && defined?(@routes) && @routes && @routes.named_routes.route_defined?(selector)
-
@controller.send(selector, *args, &block)
-
else
-
1
super
-
end
-
end
-
-
1
private
-
# Recognizes the route for a given path.
-
1
def recognized_request_for(path, extras = {}, msg)
-
if path.is_a?(Hash)
-
method = path[:method]
-
path = path[:path]
-
else
-
method = :get
-
end
-
-
# Assume given controller
-
request = ActionController::TestRequest.new
-
-
if path =~ %r{://}
-
fail_on(URI::InvalidURIError, msg) do
-
uri = URI.parse(path)
-
request.env["rack.url_scheme"] = uri.scheme || "http"
-
request.host = uri.host if uri.host
-
request.port = uri.port if uri.port
-
request.path = uri.path.to_s.empty? ? "/" : uri.path
-
end
-
else
-
path = "/#{path}" unless path.first == "/"
-
request.path = path
-
end
-
-
request.request_method = method if method
-
-
params = fail_on(ActionController::RoutingError, msg) do
-
@routes.recognize_path(path, { :method => method, :extras => extras })
-
end
-
request.path_parameters = params.with_indifferent_access
-
-
request
-
end
-
-
1
def fail_on(exception_class, message)
-
yield
-
rescue exception_class => e
-
raise Minitest::Assertion, message || e.message
-
end
-
end
-
end
-
end
-
1
require 'stringio'
-
1
require 'uri'
-
1
require 'active_support/core_ext/kernel/singleton_class'
-
1
require 'active_support/core_ext/object/try'
-
1
require 'rack/test'
-
1
require 'minitest'
-
-
1
module ActionDispatch
-
1
module Integration #:nodoc:
-
1
module RequestHelpers
-
# Performs a GET request with the given parameters.
-
#
-
# - +path+: The URI (as a String) on which you want to perform a GET
-
# request.
-
# - +parameters+: The HTTP parameters that you want to pass. This may
-
# be +nil+,
-
# a Hash, or a String that is appropriately encoded
-
# (<tt>application/x-www-form-urlencoded</tt> or
-
# <tt>multipart/form-data</tt>).
-
# - +headers_or_env+: Additional headers to pass, as a Hash. The headers will be
-
# merged into the Rack env hash.
-
#
-
# This method returns a Response object, which one can use to
-
# inspect the details of the response. Furthermore, if this method was
-
# called from an ActionDispatch::IntegrationTest object, then that
-
# object's <tt>@response</tt> instance variable will point to the same
-
# response object.
-
#
-
# You can also perform POST, PATCH, PUT, DELETE, and HEAD requests with
-
# +#post+, +#patch+, +#put+, +#delete+, and +#head+.
-
1
def get(path, parameters = nil, headers_or_env = nil)
-
process :get, path, parameters, headers_or_env
-
end
-
-
# Performs a POST request with the given parameters. See +#get+ for more
-
# details.
-
1
def post(path, parameters = nil, headers_or_env = nil)
-
process :post, path, parameters, headers_or_env
-
end
-
-
# Performs a PATCH request with the given parameters. See +#get+ for more
-
# details.
-
1
def patch(path, parameters = nil, headers_or_env = nil)
-
process :patch, path, parameters, headers_or_env
-
end
-
-
# Performs a PUT request with the given parameters. See +#get+ for more
-
# details.
-
1
def put(path, parameters = nil, headers_or_env = nil)
-
process :put, path, parameters, headers_or_env
-
end
-
-
# Performs a DELETE request with the given parameters. See +#get+ for
-
# more details.
-
1
def delete(path, parameters = nil, headers_or_env = nil)
-
process :delete, path, parameters, headers_or_env
-
end
-
-
# Performs a HEAD request with the given parameters. See +#get+ for more
-
# details.
-
1
def head(path, parameters = nil, headers_or_env = nil)
-
process :head, path, parameters, headers_or_env
-
end
-
-
# Performs an XMLHttpRequest request with the given parameters, mirroring
-
# a request from the Prototype library.
-
#
-
# The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or
-
# +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart
-
# string; the headers are a hash.
-
1
def xml_http_request(request_method, path, parameters = nil, headers_or_env = nil)
-
headers_or_env ||= {}
-
headers_or_env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
-
headers_or_env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
-
process(request_method, path, parameters, headers_or_env)
-
end
-
1
alias xhr :xml_http_request
-
-
# Follow a single redirect response. If the last response was not a
-
# redirect, an exception will be raised. Otherwise, the redirect is
-
# performed on the location header.
-
1
def follow_redirect!
-
raise "not a redirect! #{status} #{status_message}" unless redirect?
-
get(response.location)
-
status
-
end
-
-
# Performs a request using the specified method, following any subsequent
-
# redirect. Note that the redirects are followed until the response is
-
# not a redirect--this means you may run into an infinite loop if your
-
# redirect loops back to itself.
-
1
def request_via_redirect(http_method, path, parameters = nil, headers_or_env = nil)
-
process(http_method, path, parameters, headers_or_env)
-
follow_redirect! while redirect?
-
status
-
end
-
-
# Performs a GET request, following any subsequent redirect.
-
# See +request_via_redirect+ for more information.
-
1
def get_via_redirect(path, parameters = nil, headers_or_env = nil)
-
request_via_redirect(:get, path, parameters, headers_or_env)
-
end
-
-
# Performs a POST request, following any subsequent redirect.
-
# See +request_via_redirect+ for more information.
-
1
def post_via_redirect(path, parameters = nil, headers_or_env = nil)
-
request_via_redirect(:post, path, parameters, headers_or_env)
-
end
-
-
# Performs a PATCH request, following any subsequent redirect.
-
# See +request_via_redirect+ for more information.
-
1
def patch_via_redirect(path, parameters = nil, headers_or_env = nil)
-
request_via_redirect(:patch, path, parameters, headers_or_env)
-
end
-
-
# Performs a PUT request, following any subsequent redirect.
-
# See +request_via_redirect+ for more information.
-
1
def put_via_redirect(path, parameters = nil, headers_or_env = nil)
-
request_via_redirect(:put, path, parameters, headers_or_env)
-
end
-
-
# Performs a DELETE request, following any subsequent redirect.
-
# See +request_via_redirect+ for more information.
-
1
def delete_via_redirect(path, parameters = nil, headers_or_env = nil)
-
request_via_redirect(:delete, path, parameters, headers_or_env)
-
end
-
end
-
-
# An instance of this class represents a set of requests and responses
-
# performed sequentially by a test process. Because you can instantiate
-
# multiple sessions and run them side-by-side, you can also mimic (to some
-
# limited extent) multiple simultaneous users interacting with your system.
-
#
-
# Typically, you will instantiate a new session using
-
# IntegrationTest#open_session, rather than instantiating
-
# Integration::Session directly.
-
1
class Session
-
1
DEFAULT_HOST = "www.example.com"
-
-
1
include Minitest::Assertions
-
1
include TestProcess, RequestHelpers, Assertions
-
-
1
%w( status status_message headers body redirect? ).each do |method|
-
5
delegate method, :to => :response, :allow_nil => true
-
end
-
-
1
%w( path ).each do |method|
-
1
delegate method, :to => :request, :allow_nil => true
-
end
-
-
# The hostname used in the last request.
-
1
def host
-
@host || DEFAULT_HOST
-
end
-
1
attr_writer :host
-
-
# The remote_addr used in the last request.
-
1
attr_accessor :remote_addr
-
-
# The Accept header to send.
-
1
attr_accessor :accept
-
-
# A map of the cookies returned by the last response, and which will be
-
# sent with the next request.
-
1
def cookies
-
_mock_session.cookie_jar
-
end
-
-
# A reference to the controller instance used by the last request.
-
1
attr_reader :controller
-
-
# A reference to the request instance used by the last request.
-
1
attr_reader :request
-
-
# A reference to the response instance used by the last request.
-
1
attr_reader :response
-
-
# A running counter of the number of requests processed.
-
1
attr_accessor :request_count
-
-
1
include ActionDispatch::Routing::UrlFor
-
-
# Create and initialize a new Session instance.
-
1
def initialize(app)
-
super()
-
@app = app
-
-
# If the app is a Rails app, make url_helpers available on the session
-
# This makes app.url_for and app.foo_path available in the console
-
if app.respond_to?(:routes)
-
singleton_class.class_eval do
-
include app.routes.url_helpers
-
include app.routes.mounted_helpers
-
end
-
end
-
-
reset!
-
end
-
-
1
def url_options
-
@url_options ||= default_url_options.dup.tap do |url_options|
-
url_options.reverse_merge!(controller.url_options) if controller
-
-
if @app.respond_to?(:routes)
-
url_options.reverse_merge!(@app.routes.default_url_options)
-
end
-
-
url_options.reverse_merge!(:host => host, :protocol => https? ? "https" : "http")
-
end
-
end
-
-
# Resets the instance. This can be used to reset the state information
-
# in an existing session instance, so it can be used from a clean-slate
-
# condition.
-
#
-
# session.reset!
-
1
def reset!
-
@https = false
-
@controller = @request = @response = nil
-
@_mock_session = nil
-
@request_count = 0
-
@url_options = nil
-
-
self.host = DEFAULT_HOST
-
self.remote_addr = "127.0.0.1"
-
self.accept = "text/xml,application/xml,application/xhtml+xml," +
-
"text/html;q=0.9,text/plain;q=0.8,image/png," +
-
"*/*;q=0.5"
-
-
unless defined? @named_routes_configured
-
# the helpers are made protected by default--we make them public for
-
# easier access during testing and troubleshooting.
-
@named_routes_configured = true
-
end
-
end
-
-
# Specify whether or not the session should mimic a secure HTTPS request.
-
#
-
# session.https!
-
# session.https!(false)
-
1
def https!(flag = true)
-
@https = flag
-
end
-
-
# Returns +true+ if the session is mimicking a secure HTTPS request.
-
#
-
# if session.https?
-
# ...
-
# end
-
1
def https?
-
@https
-
end
-
-
# Set the host name to use in the next request.
-
#
-
# session.host! "www.example.com"
-
1
alias :host! :host=
-
-
1
private
-
1
def _mock_session
-
@_mock_session ||= Rack::MockSession.new(@app, host)
-
end
-
-
# Performs the actual request.
-
1
def process(method, path, parameters = nil, headers_or_env = nil)
-
if path =~ %r{://}
-
location = URI.parse(path)
-
https! URI::HTTPS === location if location.scheme
-
host! "#{location.host}:#{location.port}" if location.host
-
path = location.query ? "#{location.path}?#{location.query}" : location.path
-
end
-
-
hostname, port = host.split(':')
-
-
env = {
-
:method => method,
-
:params => parameters,
-
-
"SERVER_NAME" => hostname,
-
"SERVER_PORT" => port || (https? ? "443" : "80"),
-
"HTTPS" => https? ? "on" : "off",
-
"rack.url_scheme" => https? ? "https" : "http",
-
-
"REQUEST_URI" => path,
-
"HTTP_HOST" => host,
-
"REMOTE_ADDR" => remote_addr,
-
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
-
"HTTP_ACCEPT" => accept
-
}
-
# this modifies the passed env directly
-
Http::Headers.new(env).merge!(headers_or_env || {})
-
-
session = Rack::Test::Session.new(_mock_session)
-
-
# NOTE: rack-test v0.5 doesn't build a default uri correctly
-
# Make sure requested path is always a full uri
-
session.request(build_full_uri(path, env), env)
-
-
@request_count += 1
-
@request = ActionDispatch::Request.new(session.last_request.env)
-
response = _mock_session.last_response
-
@response = ActionDispatch::TestResponse.from_response(response)
-
@html_document = nil
-
@html_scanner_document = nil
-
@url_options = nil
-
-
@controller = session.last_request.env['action_controller.instance']
-
-
return response.status
-
end
-
-
1
def build_full_uri(path, env)
-
"#{env['rack.url_scheme']}://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{path}"
-
end
-
end
-
-
1
module Runner
-
1
include ActionDispatch::Assertions
-
-
1
def app
-
@app ||= nil
-
end
-
-
# Reset the current session. This is useful for testing multiple sessions
-
# in a single test case.
-
1
def reset!
-
@integration_session = Integration::Session.new(app)
-
end
-
-
1
def remove! # :nodoc:
-
@integration_session = nil
-
end
-
-
%w(get post patch put head delete cookies assigns
-
1
xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
-
12
define_method(method) do |*args|
-
reset! unless integration_session
-
-
# reset the html_document variable, except for cookies/assigns calls
-
unless method == 'cookies' || method == 'assigns'
-
@html_document = nil
-
@html_scanner_document = nil
-
reset_template_assertion
-
end
-
-
integration_session.__send__(method, *args).tap do
-
copy_session_variables!
-
end
-
end
-
end
-
-
# Open a new session instance. If a block is given, the new session is
-
# yielded to the block before being returned.
-
#
-
# session = open_session do |sess|
-
# sess.extend(CustomAssertions)
-
# end
-
#
-
# By default, a single session is automatically created for you, but you
-
# can use this method to open multiple sessions that ought to be tested
-
# simultaneously.
-
1
def open_session
-
dup.tap do |session|
-
yield session if block_given?
-
end
-
end
-
-
# Copy the instance variables from the current session instance into the
-
# test instance.
-
1
def copy_session_variables! #:nodoc:
-
return unless integration_session
-
%w(controller response request).each do |var|
-
instance_variable_set("@#{var}", @integration_session.__send__(var))
-
end
-
end
-
-
1
def default_url_options
-
reset! unless integration_session
-
integration_session.default_url_options
-
end
-
-
1
def default_url_options=(options)
-
reset! unless integration_session
-
integration_session.default_url_options = options
-
end
-
-
1
def respond_to?(method, include_private = false)
-
integration_session.respond_to?(method, include_private) || super
-
end
-
-
# Delegate unhandled messages to the current session instance.
-
1
def method_missing(sym, *args, &block)
-
reset! unless integration_session
-
if integration_session.respond_to?(sym)
-
integration_session.__send__(sym, *args, &block).tap do
-
copy_session_variables!
-
end
-
else
-
super
-
end
-
end
-
-
1
private
-
1
def integration_session
-
@integration_session ||= nil
-
end
-
end
-
end
-
-
# An integration test spans multiple controllers and actions,
-
# tying them all together to ensure they work together as expected. It tests
-
# more completely than either unit or functional tests do, exercising the
-
# entire stack, from the dispatcher to the database.
-
#
-
# At its simplest, you simply extend <tt>IntegrationTest</tt> and write your tests
-
# using the get/post methods:
-
#
-
# require "test_helper"
-
#
-
# class ExampleTest < ActionDispatch::IntegrationTest
-
# fixtures :people
-
#
-
# def test_login
-
# # get the login page
-
# get "/login"
-
# assert_equal 200, status
-
#
-
# # post the login and follow through to the home page
-
# post "/login", username: people(:jamis).username,
-
# password: people(:jamis).password
-
# follow_redirect!
-
# assert_equal 200, status
-
# assert_equal "/home", path
-
# end
-
# end
-
#
-
# However, you can also have multiple session instances open per test, and
-
# even extend those instances with assertions and methods to create a very
-
# powerful testing DSL that is specific for your application. You can even
-
# reference any named routes you happen to have defined.
-
#
-
# require "test_helper"
-
#
-
# class AdvancedTest < ActionDispatch::IntegrationTest
-
# fixtures :people, :rooms
-
#
-
# def test_login_and_speak
-
# jamis, david = login(:jamis), login(:david)
-
# room = rooms(:office)
-
#
-
# jamis.enter(room)
-
# jamis.speak(room, "anybody home?")
-
#
-
# david.enter(room)
-
# david.speak(room, "hello!")
-
# end
-
#
-
# private
-
#
-
# module CustomAssertions
-
# def enter(room)
-
# # reference a named route, for maximum internal consistency!
-
# get(room_url(id: room.id))
-
# assert(...)
-
# ...
-
# end
-
#
-
# def speak(room, message)
-
# xml_http_request "/say/#{room.id}", message: message
-
# assert(...)
-
# ...
-
# end
-
# end
-
#
-
# def login(who)
-
# open_session do |sess|
-
# sess.extend(CustomAssertions)
-
# who = people(who)
-
# sess.post "/login", username: who.username,
-
# password: who.password
-
# assert(...)
-
# end
-
# end
-
# end
-
1
class IntegrationTest < ActiveSupport::TestCase
-
1
include Integration::Runner
-
1
include ActionController::TemplateAssertions
-
1
include ActionDispatch::Routing::UrlFor
-
-
1
@@app = nil
-
-
1
def self.app
-
@@app || ActionDispatch.test_app
-
end
-
-
1
def self.app=(app)
-
@@app = app
-
end
-
-
1
def app
-
super || self.class.app
-
end
-
-
1
def url_options
-
reset! unless integration_session
-
integration_session.url_options
-
end
-
-
1
def document_root_element
-
html_document.root
-
end
-
end
-
end
-
1
require 'action_dispatch/middleware/cookies'
-
1
require 'action_dispatch/middleware/flash'
-
1
require 'active_support/core_ext/hash/indifferent_access'
-
-
1
module ActionDispatch
-
1
module TestProcess
-
1
def assigns(key = nil)
-
assigns = {}.with_indifferent_access
-
@controller.view_assigns.each { |k, v| assigns.regular_writer(k, v) }
-
key.nil? ? assigns : assigns[key]
-
end
-
-
1
def session
-
6
@request.session
-
end
-
-
1
def flash
-
4
@request.flash
-
end
-
-
1
def cookies
-
@request.cookie_jar
-
end
-
-
1
def redirect_to_url
-
@response.redirect_url
-
end
-
-
# Shortcut for <tt>Rack::Test::UploadedFile.new(File.join(ActionController::TestCase.fixture_path, path), type)</tt>:
-
#
-
# post :change_avatar, avatar: fixture_file_upload('files/spongebob.png', 'image/png')
-
#
-
# To upload binary files on Windows, pass <tt>:binary</tt> as the last parameter.
-
# This will not affect other platforms:
-
#
-
# post :change_avatar, avatar: fixture_file_upload('files/spongebob.png', 'image/png', :binary)
-
1
def fixture_file_upload(path, mime_type = nil, binary = false)
-
if self.class.respond_to?(:fixture_path) && self.class.fixture_path
-
path = File.join(self.class.fixture_path, path)
-
end
-
Rack::Test::UploadedFile.new(path, mime_type, binary)
-
end
-
end
-
end
-
1
require 'active_support/core_ext/hash/indifferent_access'
-
1
require 'rack/utils'
-
-
1
module ActionDispatch
-
1
class TestRequest < Request
-
1
DEFAULT_ENV = Rack::MockRequest.env_for('/',
-
'HTTP_HOST' => 'test.host',
-
'REMOTE_ADDR' => '0.0.0.0',
-
'HTTP_USER_AGENT' => 'Rails Testing'
-
)
-
-
1
def self.new(env = {})
-
18
super
-
end
-
-
1
def initialize(env = {})
-
18
env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
-
18
super(default_env.merge(env))
-
end
-
-
1
def request_method=(method)
-
@env['REQUEST_METHOD'] = method.to_s.upcase
-
end
-
-
1
def host=(host)
-
@env['HTTP_HOST'] = host
-
end
-
-
1
def port=(number)
-
@env['SERVER_PORT'] = number.to_i
-
end
-
-
1
def request_uri=(uri)
-
@env['REQUEST_URI'] = uri
-
end
-
-
1
def path=(path)
-
@env['PATH_INFO'] = path
-
end
-
-
1
def action=(action_name)
-
path_parameters[:action] = action_name.to_s
-
end
-
-
1
def if_modified_since=(last_modified)
-
@env['HTTP_IF_MODIFIED_SINCE'] = last_modified
-
end
-
-
1
def if_none_match=(etag)
-
@env['HTTP_IF_NONE_MATCH'] = etag
-
end
-
-
1
def remote_addr=(addr)
-
@env['REMOTE_ADDR'] = addr
-
end
-
-
1
def user_agent=(user_agent)
-
@env['HTTP_USER_AGENT'] = user_agent
-
end
-
-
1
def accept=(mime_types)
-
@env.delete('action_dispatch.request.accepts')
-
@env['HTTP_ACCEPT'] = Array(mime_types).collect { |mime_type| mime_type.to_s }.join(",")
-
end
-
-
1
alias :rack_cookies :cookies
-
-
1
def cookies
-
32
@cookies ||= {}.with_indifferent_access
-
end
-
-
1
private
-
-
1
def default_env
-
DEFAULT_ENV
-
end
-
end
-
end
-
1
module ActionDispatch
-
# Integration test methods such as ActionDispatch::Integration::Session#get
-
# and ActionDispatch::Integration::Session#post return objects of class
-
# TestResponse, which represent the HTTP response results of the requested
-
# controller actions.
-
#
-
# See Response for more information on controller response objects.
-
1
class TestResponse < Response
-
1
def self.from_response(response)
-
new response.status, response.headers, response.body, default_headers: nil
-
end
-
-
# Was the response successful?
-
1
alias_method :success?, :successful?
-
-
# Was the URL not found?
-
1
alias_method :missing?, :not_found?
-
-
# Were we redirected?
-
1
alias_method :redirect?, :redirection?
-
-
# Was there a server-side error?
-
1
alias_method :error?, :server_error?
-
end
-
end
-
1
require 'active_support/core_ext/string/output_safety'
-
-
1
module ActionView
-
1
class OutputFlow #:nodoc:
-
1
attr_reader :content
-
-
1
def initialize
-
9
@content = Hash.new { |h,k| h[k] = ActiveSupport::SafeBuffer.new }
-
end
-
-
# Called by _layout_for to read stored values.
-
1
def get(key)
-
@content[key]
-
end
-
-
# Called by each renderer object to set the layout contents.
-
1
def set(key, value)
-
9
@content[key] = value
-
end
-
-
# Called by content_for
-
1
def append(key, value)
-
@content[key] << value
-
end
-
1
alias_method :append!, :append
-
-
end
-
-
1
class StreamingFlow < OutputFlow #:nodoc:
-
1
def initialize(view, fiber)
-
@view = view
-
@parent = nil
-
@child = view.output_buffer
-
@content = view.view_flow.content
-
@fiber = fiber
-
@root = Fiber.current.object_id
-
end
-
-
# Try to get stored content. If the content
-
# is not available and we are inside the layout
-
# fiber, we set that we are waiting for the given
-
# key and yield.
-
1
def get(key)
-
return super if @content.key?(key)
-
-
if inside_fiber?
-
view = @view
-
-
begin
-
@waiting_for = key
-
view.output_buffer, @parent = @child, view.output_buffer
-
Fiber.yield
-
ensure
-
@waiting_for = nil
-
view.output_buffer, @child = @parent, view.output_buffer
-
end
-
end
-
-
super
-
end
-
-
# Appends the contents for the given key. This is called
-
# by provides and resumes back to the fiber if it is
-
# the key it is waiting for.
-
1
def append!(key, value)
-
super
-
@fiber.resume if @waiting_for == key
-
end
-
-
1
private
-
-
1
def inside_fiber?
-
Fiber.current.object_id != @root
-
end
-
end
-
end
-
1
module ActionView
-
# This class defines the interface for a renderer. Each class that
-
# subclasses +AbstractRenderer+ is used by the base +Renderer+ class to
-
# render a specific type of object.
-
#
-
# The base +Renderer+ class uses its +render+ method to delegate to the
-
# renderers. These currently consist of
-
#
-
# PartialRenderer - Used for rendering partials
-
# TemplateRenderer - Used for rendering other types of templates
-
# StreamingTemplateRenderer - Used for streaming
-
#
-
# Whenever the +render+ method is called on the base +Renderer+ class, a new
-
# renderer object of the correct type is created, and the +render+ method on
-
# that new object is called in turn. This abstracts the setup and rendering
-
# into a separate classes for partials and templates.
-
1
class AbstractRenderer #:nodoc:
-
1
delegate :find_template, :template_exists?, :with_fallbacks, :with_layout_format, :formats, :to => :@lookup_context
-
-
1
def initialize(lookup_context)
-
9
@lookup_context = lookup_context
-
end
-
-
1
def render
-
raise NotImplementedError
-
end
-
-
1
protected
-
-
1
def extract_details(options)
-
9
@lookup_context.registered_details.each_with_object({}) do |key, details|
-
36
value = options[key]
-
-
36
details[key] = Array(value) if value
-
end
-
end
-
-
1
def instrument(name, options={})
-
18
ActiveSupport::Notifications.instrument("render_#{name}.action_view", options){ yield }
-
end
-
-
1
def prepend_formats(formats)
-
9
formats = Array(formats)
-
9
return if formats.empty? || @lookup_context.html_fallback_for_js
-
-
9
@lookup_context.formats = formats | @lookup_context.formats
-
end
-
end
-
end
-
1
module ActionView
-
# This is the main entry point for rendering. It basically delegates
-
# to other objects like TemplateRenderer and PartialRenderer which
-
# actually renders the template.
-
#
-
# The Renderer will parse the options from the +render+ or +render_body+
-
# method and render a partial or a template based on the options. The
-
# +TemplateRenderer+ and +PartialRenderer+ objects are wrappers which do all
-
# the setup and logic necessary to render a view and a new object is created
-
# each time +render+ is called.
-
1
class Renderer
-
1
attr_accessor :lookup_context
-
-
1
def initialize(lookup_context)
-
9
@lookup_context = lookup_context
-
end
-
-
# Main render entry point shared by AV and AC.
-
1
def render(context, options)
-
9
if options.key?(:partial)
-
render_partial(context, options)
-
else
-
9
render_template(context, options)
-
end
-
end
-
-
# Render but returns a valid Rack body. If fibers are defined, we return
-
# a streaming body that renders the template piece by piece.
-
#
-
# Note that partials are not supported to be rendered with streaming,
-
# so in such cases, we just wrap them in an array.
-
1
def render_body(context, options)
-
if options.key?(:partial)
-
[render_partial(context, options)]
-
else
-
StreamingTemplateRenderer.new(@lookup_context).render(context, options)
-
end
-
end
-
-
# Direct accessor to template rendering.
-
1
def render_template(context, options) #:nodoc:
-
9
TemplateRenderer.new(@lookup_context).render(context, options)
-
end
-
-
# Direct access to partial rendering.
-
1
def render_partial(context, options, &block) #:nodoc:
-
PartialRenderer.new(@lookup_context).render(context, options, block)
-
end
-
end
-
end
-
1
require 'active_support/core_ext/object/try'
-
-
1
module ActionView
-
1
class TemplateRenderer < AbstractRenderer #:nodoc:
-
1
def render(context, options)
-
9
@view = context
-
9
@details = extract_details(options)
-
9
template = determine_template(options)
-
-
9
prepend_formats(template.formats)
-
-
9
@lookup_context.rendered_format ||= (template.formats.first || formats.first)
-
-
9
render_template(template, options[:layout], options[:locals])
-
end
-
-
1
private
-
-
# Determine the template to be rendered using the given options.
-
1
def determine_template(options)
-
9
keys = options.has_key?(:locals) ? options[:locals].keys : []
-
-
9
if options.key?(:body)
-
Template::Text.new(options[:body])
-
9
elsif options.key?(:text)
-
Template::Text.new(options[:text], formats.first)
-
9
elsif options.key?(:plain)
-
Template::Text.new(options[:plain])
-
9
elsif options.key?(:html)
-
Template::HTML.new(options[:html], formats.first)
-
9
elsif options.key?(:file)
-
with_fallbacks { find_template(options[:file], nil, false, keys, @details) }
-
9
elsif options.key?(:inline)
-
handler = Template.handler_for_extension(options[:type] || "erb")
-
Template.new(options[:inline], "inline template", handler, :locals => keys)
-
9
elsif options.key?(:template)
-
9
if options[:template].respond_to?(:render)
-
options[:template]
-
else
-
9
find_template(options[:template], options[:prefixes], false, keys, @details)
-
end
-
else
-
raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :text or :body option."
-
end
-
end
-
-
# Renders the given template. A string representing the layout can be
-
# supplied as well.
-
1
def render_template(template, layout_name = nil, locals = nil) #:nodoc:
-
9
view, locals = @view, locals || {}
-
-
9
render_with_layout(layout_name, locals) do |layout|
-
9
instrument(:template, :identifier => template.identifier, :layout => layout.try(:virtual_path)) do
-
9
template.render(view, locals) { |*name| view._layout_for(*name) }
-
end
-
end
-
end
-
-
1
def render_with_layout(path, locals) #:nodoc:
-
9
layout = path && find_layout(path, locals.keys)
-
9
content = yield(layout)
-
-
9
if layout
-
9
view = @view
-
9
view.view_flow.set(:layout, content)
-
9
layout.render(view, locals){ |*name| view._layout_for(*name) }
-
else
-
content
-
end
-
end
-
-
# This is the method which actually finds the layout using details in the lookup
-
# context object. If no layout is found, it checks if at least a layout with
-
# the given name exists across all details before raising the error.
-
1
def find_layout(layout, keys)
-
18
with_layout_format { resolve_layout(layout, keys) }
-
end
-
-
1
def resolve_layout(layout, keys)
-
18
case layout
-
when String
-
begin
-
if layout =~ /^\//
-
with_fallbacks { find_template(layout, nil, false, keys, @details) }
-
else
-
find_template(layout, nil, false, keys, @details)
-
end
-
rescue ActionView::MissingTemplate
-
all_details = @details.merge(:formats => @lookup_context.default_formats)
-
raise unless template_exists?(layout, nil, false, keys, all_details)
-
end
-
when Proc
-
9
resolve_layout(layout.call, keys)
-
when FalseClass
-
nil
-
else
-
9
layout
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/module/remove_method'
-
1
require 'action_controller'
-
1
require 'action_controller/test_case'
-
1
require 'action_view'
-
-
1
require 'rails-dom-testing'
-
-
1
module ActionView
-
# = Action View Test Case
-
1
class TestCase < ActiveSupport::TestCase
-
1
class TestController < ActionController::Base
-
1
include ActionDispatch::TestProcess
-
-
1
attr_accessor :request, :response, :params
-
-
1
class << self
-
1
attr_writer :controller_path
-
end
-
-
1
def controller_path=(path)
-
self.class.controller_path=(path)
-
end
-
-
1
def initialize
-
super
-
self.class.controller_path = ""
-
@request = ActionController::TestRequest.new
-
@response = ActionController::TestResponse.new
-
-
@request.env.delete('PATH_INFO')
-
@params = {}
-
end
-
end
-
-
1
module Behavior
-
1
extend ActiveSupport::Concern
-
-
1
include ActionDispatch::Assertions, ActionDispatch::TestProcess
-
1
include Rails::Dom::Testing::Assertions
-
1
include ActionController::TemplateAssertions
-
1
include ActionView::Context
-
-
1
include ActionDispatch::Routing::PolymorphicRoutes
-
-
1
include AbstractController::Helpers
-
1
include ActionView::Helpers
-
1
include ActionView::RecordIdentifier
-
1
include ActionView::RoutingUrlFor
-
-
1
include ActiveSupport::Testing::ConstantLookup
-
-
1
delegate :lookup_context, :to => :controller
-
1
attr_accessor :controller, :output_buffer, :rendered
-
-
1
module ClassMethods
-
1
def tests(helper_class)
-
case helper_class
-
when String, Symbol
-
self.helper_class = "#{helper_class.to_s.underscore}_helper".camelize.safe_constantize
-
when Module
-
self.helper_class = helper_class
-
end
-
end
-
-
1
def determine_default_helper_class(name)
-
determine_constant_from_test_name(name) do |constant|
-
Module === constant && !(Class === constant)
-
end
-
end
-
-
1
def helper_method(*methods)
-
# Almost a duplicate from ActionController::Helpers
-
methods.flatten.each do |method|
-
_helpers.module_eval <<-end_eval
-
def #{method}(*args, &block) # def current_user(*args, &block)
-
_test_case.send(%(#{method}), *args, &block) # _test_case.send(%(current_user), *args, &block)
-
end # end
-
end_eval
-
end
-
end
-
-
1
attr_writer :helper_class
-
-
1
def helper_class
-
@helper_class ||= determine_default_helper_class(name)
-
end
-
-
1
def new(*)
-
include_helper_modules!
-
super
-
end
-
-
1
private
-
-
1
def include_helper_modules!
-
helper(helper_class) if helper_class
-
include _helpers
-
end
-
-
end
-
-
1
def setup_with_controller
-
@controller = ActionView::TestCase::TestController.new
-
@request = @controller.request
-
# empty string ensures buffer has UTF-8 encoding as
-
# new without arguments returns ASCII-8BIT encoded buffer like String#new
-
@output_buffer = ActiveSupport::SafeBuffer.new ''
-
@rendered = ''
-
-
make_test_case_available_to_view!
-
say_no_to_protect_against_forgery!
-
end
-
-
1
def config
-
@controller.config if @controller.respond_to?(:config)
-
end
-
-
1
def render(options = {}, local_assigns = {}, &block)
-
view.assign(view_assigns)
-
@rendered << output = view.render(options, local_assigns, &block)
-
output
-
end
-
-
1
def rendered_views
-
@_rendered_views ||= RenderedViewsCollection.new
-
end
-
-
# Need to experiment if this priority is the best one: rendered => output_buffer
-
1
class RenderedViewsCollection
-
1
def initialize
-
@rendered_views ||= Hash.new { |hash, key| hash[key] = [] }
-
end
-
-
1
def add(view, locals)
-
@rendered_views[view] ||= []
-
@rendered_views[view] << locals
-
end
-
-
1
def locals_for(view)
-
@rendered_views[view]
-
end
-
-
1
def rendered_views
-
@rendered_views.keys
-
end
-
-
1
def view_rendered?(view, expected_locals)
-
locals_for(view).any? do |actual_locals|
-
expected_locals.all? {|key, value| value == actual_locals[key] }
-
end
-
end
-
end
-
-
1
included do
-
1
setup :setup_with_controller
-
end
-
-
1
private
-
-
# Need to experiment if this priority is the best one: rendered => output_buffer
-
1
def document_root_element
-
Nokogiri::HTML::Document.parse(@rendered.blank? ? @output_buffer : @rendered).root
-
end
-
-
1
def say_no_to_protect_against_forgery!
-
_helpers.module_eval do
-
remove_possible_method :protect_against_forgery?
-
def protect_against_forgery?
-
false
-
end
-
end
-
end
-
-
1
def make_test_case_available_to_view!
-
test_case_instance = self
-
_helpers.module_eval do
-
unless private_method_defined?(:_test_case)
-
define_method(:_test_case) { test_case_instance }
-
private :_test_case
-
end
-
end
-
end
-
-
1
module Locals
-
1
attr_accessor :rendered_views
-
-
1
def render(options = {}, local_assigns = {})
-
case options
-
when Hash
-
if block_given?
-
rendered_views.add options[:layout], options[:locals]
-
elsif options.key?(:partial)
-
rendered_views.add options[:partial], options[:locals]
-
end
-
else
-
rendered_views.add options, local_assigns
-
end
-
-
super
-
end
-
end
-
-
# The instance of ActionView::Base that is used by +render+.
-
1
def view
-
@view ||= begin
-
view = @controller.view_context
-
view.singleton_class.send :include, _helpers
-
view.extend(Locals)
-
view.rendered_views = self.rendered_views
-
view.output_buffer = self.output_buffer
-
view
-
end
-
end
-
-
1
alias_method :_view, :view
-
-
1
INTERNAL_IVARS = [
-
:@NAME,
-
:@failures,
-
:@assertions,
-
:@__io__,
-
:@_assertion_wrapped,
-
:@_assertions,
-
:@_result,
-
:@_routes,
-
:@controller,
-
:@_layouts,
-
:@_files,
-
:@_rendered_views,
-
:@method_name,
-
:@output_buffer,
-
:@_partials,
-
:@passed,
-
:@rendered,
-
:@request,
-
:@routes,
-
:@tagged_logger,
-
:@_templates,
-
:@options,
-
:@test_passed,
-
:@view,
-
:@view_context_class,
-
:@_subscribers,
-
:@html_document,
-
:@html_scanner_document
-
]
-
-
1
def _user_defined_ivars
-
instance_variables - INTERNAL_IVARS
-
end
-
-
# Returns a Hash of instance variables and their values, as defined by
-
# the user in the test case, which are then assigned to the view being
-
# rendered. This is generally intended for internal use and extension
-
# frameworks.
-
1
def view_assigns
-
Hash[_user_defined_ivars.map do |ivar|
-
[ivar[1..-1].to_sym, instance_variable_get(ivar)]
-
end]
-
end
-
-
1
def _routes
-
@controller._routes if @controller.respond_to?(:_routes)
-
end
-
-
1
def method_missing(selector, *args)
-
if @controller.respond_to?(:_routes) &&
-
( @controller._routes.named_routes.route_defined?(selector) ||
-
@controller._routes.mounted_helpers.method_defined?(selector) )
-
@controller.__send__(selector, *args)
-
else
-
super
-
end
-
end
-
end
-
-
1
include Behavior
-
end
-
end
-
1
require 'action_view/template/resolver'
-
-
1
module ActionView #:nodoc:
-
# Use FixtureResolver in your tests to simulate the presence of files on the
-
# file system. This is used internally by Rails' own test suite, and is
-
# useful for testing extensions that have no way of knowing what the file
-
# system will look like at runtime.
-
1
class FixtureResolver < PathResolver
-
1
attr_reader :hash
-
-
1
def initialize(hash = {}, pattern=nil)
-
super(pattern)
-
@hash = hash
-
end
-
-
1
def to_s
-
@hash.keys.join(', ')
-
end
-
-
1
private
-
-
1
def query(path, exts, formats)
-
query = ""
-
EXTENSIONS.each_key do |ext|
-
query << '(' << exts[ext].map {|e| e && Regexp.escape(".#{e}") }.join('|') << '|)'
-
end
-
query = /^(#{Regexp.escape(path)})#{query}$/
-
-
templates = []
-
@hash.each do |_path, array|
-
source, updated_at = array
-
next unless _path =~ query
-
handler, format, variant = extract_handler_and_format_and_variant(_path, formats)
-
templates << Template.new(source, _path, handler,
-
:virtual_path => path.virtual,
-
:format => format,
-
:variant => variant,
-
:updated_at => updated_at
-
)
-
end
-
-
templates.sort_by {|t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size }
-
end
-
end
-
-
1
class NullResolver < PathResolver
-
1
def query(path, exts, formats)
-
handler, format, variant = extract_handler_and_format_and_variant(path, formats)
-
[ActionView::Template.new("Template generated by Null Resolver", path, handler, :virtual_path => path, :format => format, :variant => variant)]
-
end
-
end
-
-
end
-
-
1
require 'active_support/core_ext/hash'
-
-
1
module ActiveJob
-
# Raised when an exception is raised during job arguments deserialization.
-
#
-
# Wraps the original exception raised as +original_exception+.
-
1
class DeserializationError < StandardError
-
1
attr_reader :original_exception
-
-
1
def initialize(e) #:nodoc:
-
super("Error while trying to deserialize arguments: #{e.message}")
-
@original_exception = e
-
set_backtrace e.backtrace
-
end
-
end
-
-
# Raised when an unsupported argument type is being set as job argument. We
-
# currently support NilClass, Fixnum, Float, String, TrueClass, FalseClass,
-
# Bignum and object that can be represented as GlobalIDs (ex: Active Record).
-
# Also raised if you set the key for a Hash something else than a string or
-
# a symbol.
-
1
class SerializationError < ArgumentError
-
end
-
-
1
module Arguments
-
1
extend self
-
1
TYPE_WHITELIST = [ NilClass, Fixnum, Float, String, TrueClass, FalseClass, Bignum ]
-
-
# Serializes a set of arguments. Whitelisted types are returned
-
# as-is. Arrays/Hashes are serialized element by element.
-
# All other types are serialized using GlobalID.
-
1
def serialize(arguments)
-
arguments.map { |argument| serialize_argument(argument) }
-
end
-
-
# Deserializes a set of arguments. Whitelisted types are returned
-
# as-is. Arrays/Hashes are deserialized element by element.
-
# All other types are deserialized using GlobalID.
-
1
def deserialize(arguments)
-
arguments.map { |argument| deserialize_argument(argument) }
-
rescue => e
-
raise DeserializationError.new(e)
-
end
-
-
1
private
-
1
GLOBALID_KEY = '_aj_globalid'.freeze
-
1
SYMBOL_KEYS_KEY = '_aj_symbol_keys'.freeze
-
1
WITH_INDIFFERENT_ACCESS_KEY = '_aj_hash_with_indifferent_access'.freeze
-
1
private_constant :GLOBALID_KEY, :SYMBOL_KEYS_KEY, :WITH_INDIFFERENT_ACCESS_KEY
-
-
1
def serialize_argument(argument)
-
case argument
-
when *TYPE_WHITELIST
-
argument
-
when GlobalID::Identification
-
{ GLOBALID_KEY => argument.to_global_id.to_s }
-
when Array
-
argument.map { |arg| serialize_argument(arg) }
-
when ActiveSupport::HashWithIndifferentAccess
-
result = serialize_hash(argument)
-
result[WITH_INDIFFERENT_ACCESS_KEY] = serialize_argument(true)
-
result
-
when Hash
-
symbol_keys = argument.each_key.grep(Symbol).map(&:to_s)
-
result = serialize_hash(argument)
-
result[SYMBOL_KEYS_KEY] = symbol_keys
-
result
-
else
-
raise SerializationError.new("Unsupported argument type: #{argument.class.name}")
-
end
-
end
-
-
1
def deserialize_argument(argument)
-
case argument
-
when String
-
GlobalID::Locator.locate(argument) || argument
-
when *TYPE_WHITELIST
-
argument
-
when Array
-
argument.map { |arg| deserialize_argument(arg) }
-
when Hash
-
if serialized_global_id?(argument)
-
deserialize_global_id argument
-
else
-
deserialize_hash(argument)
-
end
-
else
-
raise ArgumentError, "Can only deserialize primitive arguments: #{argument.inspect}"
-
end
-
end
-
-
1
def serialized_global_id?(hash)
-
hash.size == 1 and hash.include?(GLOBALID_KEY)
-
end
-
-
1
def deserialize_global_id(hash)
-
GlobalID::Locator.locate hash[GLOBALID_KEY]
-
end
-
-
1
def serialize_hash(argument)
-
argument.each_with_object({}) do |(key, value), hash|
-
hash[serialize_hash_key(key)] = serialize_argument(value)
-
end
-
end
-
-
1
def deserialize_hash(serialized_hash)
-
result = serialized_hash.transform_values { |v| deserialize_argument(v) }
-
if result.delete(WITH_INDIFFERENT_ACCESS_KEY)
-
result = result.with_indifferent_access
-
elsif symbol_keys = result.delete(SYMBOL_KEYS_KEY)
-
result = transform_symbol_keys(result, symbol_keys)
-
end
-
result
-
end
-
-
1
RESERVED_KEYS = [
-
GLOBALID_KEY, GLOBALID_KEY.to_sym,
-
SYMBOL_KEYS_KEY, SYMBOL_KEYS_KEY.to_sym,
-
WITH_INDIFFERENT_ACCESS_KEY, WITH_INDIFFERENT_ACCESS_KEY.to_sym,
-
]
-
1
private_constant :RESERVED_KEYS
-
-
1
def serialize_hash_key(key)
-
case key
-
when *RESERVED_KEYS
-
raise SerializationError.new("Can't serialize a Hash with reserved key #{key.inspect}")
-
when String, Symbol
-
key.to_s
-
else
-
raise SerializationError.new("Only string and symbol hash keys may be serialized as job arguments, but #{key.inspect} is a #{key.class}")
-
end
-
end
-
-
1
def transform_symbol_keys(hash, symbol_keys)
-
hash.transform_keys do |key|
-
if symbol_keys.include?(key)
-
key.to_sym
-
else
-
key
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_job/core'
-
1
require 'active_job/queue_adapter'
-
1
require 'active_job/queue_name'
-
1
require 'active_job/enqueuing'
-
1
require 'active_job/execution'
-
1
require 'active_job/callbacks'
-
1
require 'active_job/logging'
-
1
require 'active_job/translation'
-
-
1
module ActiveJob #:nodoc:
-
# = Active Job
-
#
-
# Active Job objects can be configured to work with different backend
-
# queuing frameworks. To specify a queue adapter to use:
-
#
-
# ActiveJob::Base.queue_adapter = :inline
-
#
-
# A list of supported adapters can be found in QueueAdapters.
-
#
-
# Active Job objects can be defined by creating a class that inherits
-
# from the ActiveJob::Base class. The only necessary method to
-
# implement is the "perform" method.
-
#
-
# To define an Active Job object:
-
#
-
# class ProcessPhotoJob < ActiveJob::Base
-
# def perform(photo)
-
# photo.watermark!('Rails')
-
# photo.rotate!(90.degrees)
-
# photo.resize_to_fit!(300, 300)
-
# photo.upload!
-
# end
-
# end
-
#
-
# Records that are passed in are serialized/deserialized using Global
-
# ID. More information can be found in Arguments.
-
#
-
# To enqueue a job to be performed as soon the queueing system is free:
-
#
-
# ProcessPhotoJob.perform_later(photo)
-
#
-
# To enqueue a job to be processed at some point in the future:
-
#
-
# ProcessPhotoJob.set(wait_until: Date.tomorrow.noon).perform_later(photo)
-
#
-
# More information can be found in ActiveJob::Core::ClassMethods#set
-
#
-
# A job can also be processed immediately without sending to the queue:
-
#
-
# ProcessPhotoJob.perform_now(photo)
-
#
-
# == Exceptions
-
#
-
# * DeserializationError - Error class for deserialization errors.
-
# * SerializationError - Error class for serialization errors.
-
1
class Base
-
1
include Core
-
1
include QueueAdapter
-
1
include QueueName
-
1
include Enqueuing
-
1
include Execution
-
1
include Callbacks
-
1
include Logging
-
1
include Translation
-
-
1
ActiveSupport.run_load_hooks(:active_job, self)
-
end
-
end
-
1
require 'active_support/callbacks'
-
-
1
module ActiveJob
-
# = Active Job Callbacks
-
#
-
# Active Job provides hooks during the lifecycle of a job. Callbacks allow you
-
# to trigger logic during the lifecycle of a job. Available callbacks are:
-
#
-
# * <tt>before_enqueue</tt>
-
# * <tt>around_enqueue</tt>
-
# * <tt>after_enqueue</tt>
-
# * <tt>before_perform</tt>
-
# * <tt>around_perform</tt>
-
# * <tt>after_perform</tt>
-
#
-
1
module Callbacks
-
1
extend ActiveSupport::Concern
-
1
include ActiveSupport::Callbacks
-
-
1
included do
-
1
define_callbacks :perform
-
1
define_callbacks :enqueue
-
end
-
-
# These methods will be included into any Active Job object, adding
-
# callbacks for +perform+ and +enqueue+ methods.
-
1
module ClassMethods
-
# Defines a callback that will get called right before the
-
# job's perform method is executed.
-
#
-
# class VideoProcessJob < ActiveJob::Base
-
# queue_as :default
-
#
-
# before_perform do |job|
-
# UserMailer.notify_video_started_processing(job.arguments.first)
-
# end
-
#
-
# def perform(video_id)
-
# Video.find(video_id).process
-
# end
-
# end
-
#
-
1
def before_perform(*filters, &blk)
-
set_callback(:perform, :before, *filters, &blk)
-
end
-
-
# Defines a callback that will get called right after the
-
# job's perform method has finished.
-
#
-
# class VideoProcessJob < ActiveJob::Base
-
# queue_as :default
-
#
-
# after_perform do |job|
-
# UserMailer.notify_video_processed(job.arguments.first)
-
# end
-
#
-
# def perform(video_id)
-
# Video.find(video_id).process
-
# end
-
# end
-
#
-
1
def after_perform(*filters, &blk)
-
set_callback(:perform, :after, *filters, &blk)
-
end
-
-
# Defines a callback that will get called around the job's perform method.
-
#
-
# class VideoProcessJob < ActiveJob::Base
-
# queue_as :default
-
#
-
# around_perform do |job, block|
-
# UserMailer.notify_video_started_processing(job.arguments.first)
-
# block.call
-
# UserMailer.notify_video_processed(job.arguments.first)
-
# end
-
#
-
# def perform(video_id)
-
# Video.find(video_id).process
-
# end
-
# end
-
#
-
1
def around_perform(*filters, &blk)
-
2
set_callback(:perform, :around, *filters, &blk)
-
end
-
-
# Defines a callback that will get called right before the
-
# job is enqueued.
-
#
-
# class VideoProcessJob < ActiveJob::Base
-
# queue_as :default
-
#
-
# before_enqueue do |job|
-
# $statsd.increment "enqueue-video-job.try"
-
# end
-
#
-
# def perform(video_id)
-
# Video.find(video_id).process
-
# end
-
# end
-
#
-
1
def before_enqueue(*filters, &blk)
-
set_callback(:enqueue, :before, *filters, &blk)
-
end
-
-
# Defines a callback that will get called right after the
-
# job is enqueued.
-
#
-
# class VideoProcessJob < ActiveJob::Base
-
# queue_as :default
-
#
-
# after_enqueue do |job|
-
# $statsd.increment "enqueue-video-job.success"
-
# end
-
#
-
# def perform(video_id)
-
# Video.find(video_id).process
-
# end
-
# end
-
#
-
1
def after_enqueue(*filters, &blk)
-
1
set_callback(:enqueue, :after, *filters, &blk)
-
end
-
-
# Defines a callback that will get called before and after the
-
# job is enqueued.
-
#
-
# class VideoProcessJob < ActiveJob::Base
-
# queue_as :default
-
#
-
# around_enqueue do |job, block|
-
# $statsd.time "video-job.process" do
-
# block.call
-
# end
-
# end
-
#
-
# def perform(video_id)
-
# Video.find(video_id).process
-
# end
-
# end
-
#
-
1
def around_enqueue(*filters, &blk)
-
1
set_callback(:enqueue, :around, *filters, &blk)
-
end
-
end
-
end
-
end
-
1
module ActiveJob
-
1
module Core
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
# Job arguments
-
1
attr_accessor :arguments
-
1
attr_writer :serialized_arguments
-
-
# Timestamp when the job should be performed
-
1
attr_accessor :scheduled_at
-
-
# Job Identifier
-
1
attr_accessor :job_id
-
-
# Queue in which the job will reside.
-
1
attr_writer :queue_name
-
-
# I18n.locale to be used during the job.
-
1
attr_accessor :locale
-
end
-
-
# These methods will be included into any Active Job object, adding
-
# helpers for de/serialization and creation of job instances.
-
1
module ClassMethods
-
# Creates a new job instance from a hash created with +serialize+
-
1
def deserialize(job_data)
-
job = job_data['job_class'].constantize.new
-
job.job_id = job_data['job_id']
-
job.queue_name = job_data['queue_name']
-
job.serialized_arguments = job_data['arguments']
-
job.locale = job_data['locale'] || I18n.locale
-
job
-
end
-
-
# Creates a job preconfigured with the given options. You can call
-
# perform_later with the job arguments to enqueue the job with the
-
# preconfigured options
-
#
-
# ==== Options
-
# * <tt>:wait</tt> - Enqueues the job with the specified delay
-
# * <tt>:wait_until</tt> - Enqueues the job at the time specified
-
# * <tt>:queue</tt> - Enqueues the job on the specified queue
-
#
-
# ==== Examples
-
#
-
# VideoJob.set(queue: :some_queue).perform_later(Video.last)
-
# VideoJob.set(wait: 5.minutes).perform_later(Video.last)
-
# VideoJob.set(wait_until: Time.now.tomorrow).perform_later(Video.last)
-
# VideoJob.set(queue: :some_queue, wait: 5.minutes).perform_later(Video.last)
-
# VideoJob.set(queue: :some_queue, wait_until: Time.now.tomorrow).perform_later(Video.last)
-
1
def set(options={})
-
ConfiguredJob.new(self, options)
-
end
-
end
-
-
# Creates a new job instance. Takes the arguments that will be
-
# passed to the perform method.
-
1
def initialize(*arguments)
-
@arguments = arguments
-
@job_id = SecureRandom.uuid
-
@queue_name = self.class.queue_name
-
end
-
-
# Returns a hash with the job data that can safely be passed to the
-
# queueing adapter.
-
1
def serialize
-
{
-
'job_class' => self.class.name,
-
'job_id' => job_id,
-
'queue_name' => queue_name,
-
'arguments' => serialize_arguments(arguments),
-
'locale' => I18n.locale
-
}
-
end
-
-
1
private
-
1
def deserialize_arguments_if_needed
-
if defined?(@serialized_arguments) && @serialized_arguments.present?
-
@arguments = deserialize_arguments(@serialized_arguments)
-
@serialized_arguments = nil
-
end
-
end
-
-
1
def serialize_arguments(serialized_args)
-
Arguments.serialize(serialized_args)
-
end
-
-
1
def deserialize_arguments(serialized_args)
-
Arguments.deserialize(serialized_args)
-
end
-
end
-
end
-
1
require 'active_job/arguments'
-
-
1
module ActiveJob
-
1
module Enqueuing
-
1
extend ActiveSupport::Concern
-
-
# Includes the +perform_later+ method for job initialization.
-
1
module ClassMethods
-
# Push a job onto the queue. The arguments must be legal JSON types
-
# (string, int, float, nil, true, false, hash or array) or
-
# GlobalID::Identification instances. Arbitrary Ruby objects
-
# are not supported.
-
#
-
# Returns an instance of the job class queued with arguments available in
-
# Job#arguments.
-
1
def perform_later(*args)
-
job_or_instantiate(*args).enqueue
-
end
-
-
1
protected
-
1
def job_or_instantiate(*args)
-
args.first.is_a?(self) ? args.first : new(*args)
-
end
-
end
-
-
# Reschedules the job to be re-executed. This is useful in combination
-
# with the +rescue_from+ option. When you rescue an exception from your job
-
# you can ask Active Job to retry performing your job.
-
#
-
# ==== Options
-
# * <tt>:wait</tt> - Enqueues the job with the specified delay
-
# * <tt>:wait_until</tt> - Enqueues the job at the time specified
-
# * <tt>:queue</tt> - Enqueues the job on the specified queue
-
#
-
# ==== Examples
-
#
-
# class SiteScrapperJob < ActiveJob::Base
-
# rescue_from(ErrorLoadingSite) do
-
# retry_job queue: :low_priority
-
# end
-
#
-
# def perform(*args)
-
# # raise ErrorLoadingSite if cannot scrape
-
# end
-
# end
-
1
def retry_job(options={})
-
enqueue options
-
end
-
-
# Enqueues the job to be performed by the queue adapter.
-
#
-
# ==== Options
-
# * <tt>:wait</tt> - Enqueues the job with the specified delay
-
# * <tt>:wait_until</tt> - Enqueues the job at the time specified
-
# * <tt>:queue</tt> - Enqueues the job on the specified queue
-
#
-
# ==== Examples
-
#
-
# my_job_instance.enqueue
-
# my_job_instance.enqueue wait: 5.minutes
-
# my_job_instance.enqueue queue: :important
-
# my_job_instance.enqueue wait_until: Date.tomorrow.midnight
-
1
def enqueue(options={})
-
self.scheduled_at = options[:wait].seconds.from_now.to_f if options[:wait]
-
self.scheduled_at = options[:wait_until].to_f if options[:wait_until]
-
self.queue_name = self.class.queue_name_from_part(options[:queue]) if options[:queue]
-
run_callbacks :enqueue do
-
if self.scheduled_at
-
self.class.queue_adapter.enqueue_at self, self.scheduled_at
-
else
-
self.class.queue_adapter.enqueue self
-
end
-
end
-
self
-
end
-
end
-
end
-
1
require 'active_support/rescuable'
-
1
require 'active_job/arguments'
-
-
1
module ActiveJob
-
1
module Execution
-
1
extend ActiveSupport::Concern
-
1
include ActiveSupport::Rescuable
-
-
# Includes methods for executing and performing jobs instantly.
-
1
module ClassMethods
-
# Performs the job immediately.
-
#
-
# MyJob.perform_now("mike")
-
#
-
1
def perform_now(*args)
-
job_or_instantiate(*args).perform_now
-
end
-
-
1
def execute(job_data) #:nodoc:
-
job = deserialize(job_data)
-
job.perform_now
-
end
-
end
-
-
# Performs the job immediately. The job is not sent to the queueing adapter
-
# but directly executed by blocking the execution of others until it's finished.
-
#
-
# MyJob.new(*args).perform_now
-
1
def perform_now
-
deserialize_arguments_if_needed
-
run_callbacks :perform do
-
perform(*arguments)
-
end
-
rescue => exception
-
rescue_with_handler(exception) || raise(exception)
-
end
-
-
1
def perform(*)
-
fail NotImplementedError
-
end
-
end
-
end
-
1
require 'active_support/core_ext/string/filters'
-
1
require 'active_support/tagged_logging'
-
1
require 'active_support/logger'
-
-
1
module ActiveJob
-
1
module Logging #:nodoc:
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
3
cattr_accessor(:logger) { ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT)) }
-
-
1
around_enqueue do |_, block, _|
-
tag_logger do
-
block.call
-
end
-
end
-
-
1
around_perform do |job, block, _|
-
tag_logger(job.class.name, job.job_id) do
-
payload = {adapter: job.class.queue_adapter, job: job}
-
ActiveSupport::Notifications.instrument("perform_start.active_job", payload.dup)
-
ActiveSupport::Notifications.instrument("perform.active_job", payload) do
-
block.call
-
end
-
end
-
end
-
-
1
after_enqueue do |job|
-
if job.scheduled_at
-
ActiveSupport::Notifications.instrument "enqueue_at.active_job",
-
adapter: job.class.queue_adapter, job: job
-
else
-
ActiveSupport::Notifications.instrument "enqueue.active_job",
-
adapter: job.class.queue_adapter, job: job
-
end
-
end
-
end
-
-
1
private
-
1
def tag_logger(*tags)
-
if logger.respond_to?(:tagged)
-
tags.unshift "ActiveJob" unless logger_tagged_by_active_job?
-
ActiveJob::Base.logger.tagged(*tags){ yield }
-
else
-
yield
-
end
-
end
-
-
1
def logger_tagged_by_active_job?
-
logger.formatter.current_tags.include?("ActiveJob")
-
end
-
-
1
class LogSubscriber < ActiveSupport::LogSubscriber #:nodoc:
-
1
def enqueue(event)
-
info do
-
job = event.payload[:job]
-
"Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)}" + args_info(job)
-
end
-
end
-
-
1
def enqueue_at(event)
-
info do
-
job = event.payload[:job]
-
"Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)} at #{scheduled_at(event)}" + args_info(job)
-
end
-
end
-
-
1
def perform_start(event)
-
info do
-
job = event.payload[:job]
-
"Performing #{job.class.name} from #{queue_name(event)}" + args_info(job)
-
end
-
end
-
-
1
def perform(event)
-
info do
-
job = event.payload[:job]
-
"Performed #{job.class.name} from #{queue_name(event)} in #{event.duration.round(2)}ms"
-
end
-
end
-
-
1
private
-
1
def queue_name(event)
-
event.payload[:adapter].name.demodulize.remove('Adapter') + "(#{event.payload[:job].queue_name})"
-
end
-
-
1
def args_info(job)
-
if job.arguments.any?
-
' with arguments: ' +
-
job.arguments.map { |arg| arg.try(:to_global_id).try(:to_s) || arg.inspect }.join(', ')
-
else
-
''
-
end
-
end
-
-
1
def scheduled_at(event)
-
Time.at(event.payload[:job].scheduled_at).utc
-
end
-
-
1
def logger
-
ActiveJob::Base.logger
-
end
-
end
-
end
-
end
-
-
1
ActiveJob::Logging::LogSubscriber.attach_to :active_job
-
1
require 'active_job/queue_adapters/inline_adapter'
-
1
require 'active_support/core_ext/string/inflections'
-
-
1
module ActiveJob
-
# The <tt>ActionJob::QueueAdapter</tt> module is used to load the
-
# correct adapter. The default queue adapter is the :inline queue.
-
1
module QueueAdapter #:nodoc:
-
1
extend ActiveSupport::Concern
-
-
# Includes the setter method for changing the active queue adapter.
-
1
module ClassMethods
-
2
mattr_reader(:queue_adapter) { ActiveJob::QueueAdapters::InlineAdapter }
-
-
# Specify the backend queue provider. The default queue adapter
-
# is the :inline queue. See QueueAdapters for more
-
# information.
-
1
def queue_adapter=(name_or_adapter)
-
1
@@queue_adapter = \
-
case name_or_adapter
-
when :test
-
ActiveJob::QueueAdapters::TestAdapter.new
-
when Symbol, String
-
1
load_adapter(name_or_adapter)
-
else
-
name_or_adapter if name_or_adapter.respond_to?(:enqueue)
-
end
-
end
-
-
1
private
-
1
def load_adapter(name)
-
1
"ActiveJob::QueueAdapters::#{name.to_s.camelize}Adapter".constantize
-
end
-
end
-
end
-
end
-
1
module ActiveJob
-
# == Active Job adapters
-
#
-
# Active Job has adapters for the following queueing backends:
-
#
-
# * {Backburner}[https://github.com/nesquena/backburner]
-
# * {Delayed Job}[https://github.com/collectiveidea/delayed_job]
-
# * {Qu}[https://github.com/bkeepers/qu]
-
# * {Que}[https://github.com/chanks/que]
-
# * {queue_classic}[https://github.com/QueueClassic/queue_classic]
-
# * {Resque 1.x}[https://github.com/resque/resque/tree/1-x-stable]
-
# * {Sidekiq}[http://sidekiq.org]
-
# * {Sneakers}[https://github.com/jondot/sneakers]
-
# * {Sucker Punch}[https://github.com/brandonhilkert/sucker_punch]
-
#
-
# === Backends Features
-
#
-
# | | Async | Queues | Delayed | Priorities | Timeout | Retries |
-
# |-------------------|-------|--------|------------|------------|---------|---------|
-
# | Backburner | Yes | Yes | Yes | Yes | Job | Global |
-
# | Delayed Job | Yes | Yes | Yes | Job | Global | Global |
-
# | Qu | Yes | Yes | No | No | No | Global |
-
# | Que | Yes | Yes | Yes | Job | No | Job |
-
# | queue_classic | Yes | Yes | Yes* | No | No | No |
-
# | Resque | Yes | Yes | Yes (Gem) | Queue | Global | Yes |
-
# | Sidekiq | Yes | Yes | Yes | Queue | No | Job |
-
# | Sneakers | Yes | Yes | No | Queue | Queue | No |
-
# | Sucker Punch | Yes | Yes | No | No | No | No |
-
# | Active Job Inline | No | Yes | N/A | N/A | N/A | N/A |
-
#
-
# NOTE:
-
# queue_classic supports job scheduling since version 3.1.
-
# For older versions you can use the queue_classic-later gem.
-
#
-
1
module QueueAdapters
-
1
extend ActiveSupport::Autoload
-
-
1
autoload :InlineAdapter
-
1
autoload :BackburnerAdapter
-
1
autoload :DelayedJobAdapter
-
1
autoload :QuAdapter
-
1
autoload :QueAdapter
-
1
autoload :QueueClassicAdapter
-
1
autoload :ResqueAdapter
-
1
autoload :SidekiqAdapter
-
1
autoload :SneakersAdapter
-
1
autoload :SuckerPunchAdapter
-
1
autoload :TestAdapter
-
end
-
end
-
1
module ActiveJob
-
1
module QueueAdapters
-
# == Active Job Inline adapter
-
#
-
# When enqueueing jobs with the Inline adapter the job will be executed
-
# immediately.
-
#
-
# To use the Inline set the queue_adapter config to +:inline+.
-
#
-
# Rails.application.config.active_job.queue_adapter = :inline
-
1
class InlineAdapter
-
1
class << self
-
1
def enqueue(job) #:nodoc:
-
Base.execute(job.serialize)
-
end
-
-
1
def enqueue_at(*) #:nodoc:
-
raise NotImplementedError.new("Use a queueing backend to enqueue jobs in the future. Read more at http://guides.rubyonrails.org/active_job_basics.html")
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveJob
-
1
module QueueName
-
1
extend ActiveSupport::Concern
-
-
# Includes the ability to override the default queue name and prefix.
-
1
module ClassMethods
-
1
mattr_accessor(:queue_name_prefix)
-
3
mattr_accessor(:default_queue_name) { "default" }
-
-
# Specifies the name of the queue to process the job on.
-
#
-
# class PublishToFeedJob < ActiveJob::Base
-
# queue_as :feeds
-
#
-
# def perform(post)
-
# post.to_feed!
-
# end
-
# end
-
1
def queue_as(part_name=nil, &block)
-
if block_given?
-
self.queue_name = block
-
else
-
self.queue_name = queue_name_from_part(part_name)
-
end
-
end
-
-
1
def queue_name_from_part(part_name) #:nodoc:
-
queue_name = part_name || default_queue_name
-
name_parts = [queue_name_prefix.presence, queue_name]
-
name_parts.compact.join(queue_name_delimiter)
-
end
-
end
-
-
1
included do
-
1
class_attribute :queue_name, instance_accessor: false
-
1
class_attribute :queue_name_delimiter, instance_accessor: false
-
-
1
self.queue_name = default_queue_name
-
1
self.queue_name_delimiter = '_' # set default delimiter to '_'
-
end
-
-
# Returns the name of the queue the job will be run on
-
1
def queue_name
-
if @queue_name.is_a?(Proc)
-
@queue_name = self.class.queue_name_from_part(instance_exec(&@queue_name))
-
end
-
@queue_name
-
end
-
-
end
-
end
-
1
module ActiveJob
-
1
module Translation #:nodoc:
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
around_perform do |job, block, _|
-
I18n.with_locale(job.locale, &block)
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/array/extract_options'
-
-
1
module ActiveModel
-
# == Active \Model \Callbacks
-
#
-
# Provides an interface for any class to have Active Record like callbacks.
-
#
-
# Like the Active Record methods, the callback chain is aborted as soon as
-
# one of the methods in the chain returns +false+.
-
#
-
# First, extend ActiveModel::Callbacks from the class you are creating:
-
#
-
# class MyModel
-
# extend ActiveModel::Callbacks
-
# end
-
#
-
# Then define a list of methods that you want callbacks attached to:
-
#
-
# define_model_callbacks :create, :update
-
#
-
# This will provide all three standard callbacks (before, around and after)
-
# for both the <tt>:create</tt> and <tt>:update</tt> methods. To implement,
-
# you need to wrap the methods you want callbacks on in a block so that the
-
# callbacks get a chance to fire:
-
#
-
# def create
-
# run_callbacks :create do
-
# # Your create action methods here
-
# end
-
# end
-
#
-
# Then in your class, you can use the +before_create+, +after_create+ and
-
# +around_create+ methods, just as you would in an Active Record model.
-
#
-
# before_create :action_before_create
-
#
-
# def action_before_create
-
# # Your code here
-
# end
-
#
-
# When defining an around callback remember to yield to the block, otherwise
-
# it won't be executed:
-
#
-
# around_create :log_status
-
#
-
# def log_status
-
# puts 'going to call the block...'
-
# yield
-
# puts 'block successfully called.'
-
# end
-
#
-
# You can choose not to have all three callbacks by passing a hash to the
-
# +define_model_callbacks+ method.
-
#
-
# define_model_callbacks :create, only: [:after, :before]
-
#
-
# Would only create the +after_create+ and +before_create+ callback methods in
-
# your class.
-
1
module Callbacks
-
1
def self.extended(base) #:nodoc:
-
1
base.class_eval do
-
1
include ActiveSupport::Callbacks
-
end
-
end
-
-
# define_model_callbacks accepts the same options +define_callbacks+ does,
-
# in case you want to overwrite a default. Besides that, it also accepts an
-
# <tt>:only</tt> option, where you can choose if you want all types (before,
-
# around or after) or just some.
-
#
-
# define_model_callbacks :initializer, only: :after
-
#
-
# Note, the <tt>only: <type></tt> hash will apply to all callbacks defined
-
# on that method call. To get around this you can call the define_model_callbacks
-
# method as many times as you need.
-
#
-
# define_model_callbacks :create, only: :after
-
# define_model_callbacks :update, only: :before
-
# define_model_callbacks :destroy, only: :around
-
#
-
# Would create +after_create+, +before_update+ and +around_destroy+ methods
-
# only.
-
#
-
# You can pass in a class to before_<type>, after_<type> and around_<type>,
-
# in which case the callback will call that class's <action>_<type> method
-
# passing the object that the callback is being called on.
-
#
-
# class MyModel
-
# extend ActiveModel::Callbacks
-
# define_model_callbacks :create
-
#
-
# before_create AnotherClass
-
# end
-
#
-
# class AnotherClass
-
# def self.before_create( obj )
-
# # obj is the MyModel instance that the callback is being called on
-
# end
-
# end
-
#
-
# NOTE: +method_name+ passed to `define_model_callbacks` must not end with
-
# `!`, `?` or `=`.
-
1
def define_model_callbacks(*callbacks)
-
2
options = callbacks.extract_options!
-
2
options = {
-
24
terminator: ->(_,result) { result == false },
-
skip_after_callbacks_if_terminated: true,
-
scope: [:kind, :name],
-
only: [:before, :around, :after]
-
}.merge!(options)
-
-
2
types = Array(options.delete(:only))
-
-
2
callbacks.each do |callback|
-
7
define_callbacks(callback, options)
-
-
7
types.each do |type|
-
15
send("_define_#{type}_model_callback", self, callback)
-
end
-
end
-
end
-
-
1
private
-
-
1
def _define_before_model_callback(klass, callback) #:nodoc:
-
4
klass.define_singleton_method("before_#{callback}") do |*args, &block|
-
3
set_callback(:"#{callback}", :before, *args, &block)
-
end
-
end
-
-
1
def _define_around_model_callback(klass, callback) #:nodoc:
-
4
klass.define_singleton_method("around_#{callback}") do |*args, &block|
-
set_callback(:"#{callback}", :around, *args, &block)
-
end
-
end
-
-
1
def _define_after_model_callback(klass, callback) #:nodoc:
-
7
klass.define_singleton_method("after_#{callback}") do |*args, &block|
-
2
options = args.extract_options!
-
2
options[:prepend] = true
-
2
conditional = ActiveSupport::Callbacks::Conditionals::Value.new { |v|
-
23
v != false
-
}
-
2
options[:if] = Array(options[:if]) << conditional
-
2
set_callback(:"#{callback}", :after, *(args << options), &block)
-
end
-
end
-
end
-
end
-
1
module ActiveModel
-
# == Active \Model \Conversion
-
#
-
# Handles default conversions: to_model, to_key, to_param, and to_partial_path.
-
#
-
# Let's take for example this non-persisted object.
-
#
-
# class ContactMessage
-
# include ActiveModel::Conversion
-
#
-
# # ContactMessage are never persisted in the DB
-
# def persisted?
-
# false
-
# end
-
# end
-
#
-
# cm = ContactMessage.new
-
# cm.to_model == cm # => true
-
# cm.to_key # => nil
-
# cm.to_param # => nil
-
# cm.to_partial_path # => "contact_messages/contact_message"
-
1
module Conversion
-
1
extend ActiveSupport::Concern
-
-
# If your object is already designed to implement all of the Active Model
-
# you can use the default <tt>:to_model</tt> implementation, which simply
-
# returns +self+.
-
#
-
# class Person
-
# include ActiveModel::Conversion
-
# end
-
#
-
# person = Person.new
-
# person.to_model == person # => true
-
#
-
# If your model does not act like an Active Model object, then you should
-
# define <tt>:to_model</tt> yourself returning a proxy object that wraps
-
# your object with Active Model compliant methods.
-
1
def to_model
-
6
self
-
end
-
-
# Returns an Array of all key attributes if any is set, regardless if
-
# the object is persisted or not. Returns +nil+ if there are no key attributes.
-
#
-
# class Person
-
# include ActiveModel::Conversion
-
# attr_accessor :id
-
# end
-
#
-
# person = Person.create(id: 1)
-
# person.to_key # => [1]
-
1
def to_key
-
key = respond_to?(:id) && id
-
key ? [key] : nil
-
end
-
-
# Returns a +string+ representing the object's key suitable for use in URLs,
-
# or +nil+ if <tt>persisted?</tt> is +false+.
-
#
-
# class Person
-
# include ActiveModel::Conversion
-
# attr_accessor :id
-
# def persisted?
-
# true
-
# end
-
# end
-
#
-
# person = Person.create(id: 1)
-
# person.to_param # => "1"
-
1
def to_param
-
(persisted? && key = to_key) ? key.join('-') : nil
-
end
-
-
# Returns a +string+ identifying the path associated with the object.
-
# ActionPack uses this to find a suitable partial to represent the object.
-
#
-
# class Person
-
# include ActiveModel::Conversion
-
# end
-
#
-
# person = Person.new
-
# person.to_partial_path # => "people/person"
-
1
def to_partial_path
-
self.class._to_partial_path
-
end
-
-
1
module ClassMethods #:nodoc:
-
# Provide a class level cache for #to_partial_path. This is an
-
# internal method and should not be accessed directly.
-
1
def _to_partial_path #:nodoc:
-
@_to_partial_path ||= begin
-
element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(name))
-
collection = ActiveSupport::Inflector.tableize(name)
-
"#{collection}/#{element}".freeze
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/hash_with_indifferent_access'
-
1
require 'active_support/core_ext/object/duplicable'
-
1
require 'active_support/core_ext/string/filters'
-
-
1
module ActiveModel
-
# == Active \Model \Dirty
-
#
-
# Provides a way to track changes in your object in the same way as
-
# Active Record does.
-
#
-
# The requirements for implementing ActiveModel::Dirty are:
-
#
-
# * <tt>include ActiveModel::Dirty</tt> in your object.
-
# * Call <tt>define_attribute_methods</tt> passing each method you want to
-
# track.
-
# * Call <tt>attr_name_will_change!</tt> before each change to the tracked
-
# attribute.
-
# * Call <tt>changes_applied</tt> after the changes are persisted.
-
# * Call <tt>clear_changes_information</tt> when you want to reset the changes
-
# information.
-
# * Call <tt>restore_attributes</tt> when you want to restore previous data.
-
#
-
# A minimal implementation could be:
-
#
-
# class Person
-
# include ActiveModel::Dirty
-
#
-
# define_attribute_methods :name
-
#
-
# def name
-
# @name
-
# end
-
#
-
# def name=(val)
-
# name_will_change! unless val == @name
-
# @name = val
-
# end
-
#
-
# def save
-
# # do persistence work
-
#
-
# changes_applied
-
# end
-
#
-
# def reload!
-
# # get the values from the persistence layer
-
#
-
# clear_changes_information
-
# end
-
#
-
# def rollback!
-
# restore_attributes
-
# end
-
# end
-
#
-
# A newly instantiated +Person+ object is unchanged:
-
#
-
# person = Person.new
-
# person.changed? # => false
-
#
-
# Change the name:
-
#
-
# person.name = 'Bob'
-
# person.changed? # => true
-
# person.name_changed? # => true
-
# person.name_changed?(from: "Uncle Bob", to: "Bob") # => true
-
# person.name_was # => "Uncle Bob"
-
# person.name_change # => ["Uncle Bob", "Bob"]
-
# person.name = 'Bill'
-
# person.name_change # => ["Uncle Bob", "Bill"]
-
#
-
# Save the changes:
-
#
-
# person.save
-
# person.changed? # => false
-
# person.name_changed? # => false
-
#
-
# Reset the changes:
-
#
-
# person.previous_changes # => {"name" => ["Uncle Bob", "Bill"]}
-
# person.reload!
-
# person.previous_changes # => {}
-
#
-
# Rollback the changes:
-
#
-
# person.name = "Uncle Bob"
-
# person.rollback!
-
# person.name # => "Bill"
-
# person.name_changed? # => false
-
#
-
# Assigning the same value leaves the attribute unchanged:
-
#
-
# person.name = 'Bill'
-
# person.name_changed? # => false
-
# person.name_change # => nil
-
#
-
# Which attributes have changed?
-
#
-
# person.name = 'Bob'
-
# person.changed # => ["name"]
-
# person.changes # => {"name" => ["Bill", "Bob"]}
-
#
-
# If an attribute is modified in-place then make use of
-
# +[attribute_name]_will_change!+ to mark that the attribute is changing.
-
# Otherwise Active Model can't track changes to in-place attributes. Note
-
# that Active Record can detect in-place modifications automatically. You do
-
# not need to call +[attribute_name]_will_change!+ on Active Record models.
-
#
-
# person.name_will_change!
-
# person.name_change # => ["Bill", "Bill"]
-
# person.name << 'y'
-
# person.name_change # => ["Bill", "Billy"]
-
1
module Dirty
-
1
extend ActiveSupport::Concern
-
1
include ActiveModel::AttributeMethods
-
-
1
included do
-
1
attribute_method_suffix '_changed?', '_change', '_will_change!', '_was'
-
1
attribute_method_affix prefix: 'reset_', suffix: '!'
-
1
attribute_method_affix prefix: 'restore_', suffix: '!'
-
end
-
-
# Returns +true+ if any attribute have unsaved changes, +false+ otherwise.
-
#
-
# person.changed? # => false
-
# person.name = 'bob'
-
# person.changed? # => true
-
1
def changed?
-
3
changed_attributes.present?
-
end
-
-
# Returns an array with the name of the attributes with unsaved changes.
-
#
-
# person.changed # => []
-
# person.name = 'bob'
-
# person.changed # => ["name"]
-
1
def changed
-
62
changed_attributes.keys
-
end
-
-
# Returns a hash of changed attributes indicating their original
-
# and new values like <tt>attr => [original value, new value]</tt>.
-
#
-
# person.changes # => {}
-
# person.name = 'bob'
-
# person.changes # => { "name" => ["bill", "bob"] }
-
1
def changes
-
414
ActiveSupport::HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
-
end
-
-
# Returns a hash of attributes that were changed before the model was saved.
-
#
-
# person.name # => "bob"
-
# person.name = 'robert'
-
# person.save
-
# person.previous_changes # => {"name" => ["bob", "robert"]}
-
1
def previous_changes
-
@previously_changed ||= ActiveSupport::HashWithIndifferentAccess.new
-
end
-
-
# Returns a hash of the attributes with unsaved changes indicating their original
-
# values like <tt>attr => original value</tt>.
-
#
-
# person.name # => "bob"
-
# person.name = 'robert'
-
# person.changed_attributes # => {"name" => "bob"}
-
1
def changed_attributes
-
2001
@changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
-
end
-
-
# Handle <tt>*_changed?</tt> for +method_missing+.
-
1
def attribute_changed?(attr, options = {}) #:nodoc:
-
907
result = changes_include?(attr)
-
907
result &&= options[:to] == __send__(attr) if options.key?(:to)
-
907
result &&= options[:from] == changed_attributes[attr] if options.key?(:from)
-
907
result
-
end
-
-
# Handle <tt>*_was</tt> for +method_missing+.
-
1
def attribute_was(attr) # :nodoc:
-
2
attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
-
end
-
-
# Restore all previous data of the provided attributes.
-
1
def restore_attributes(attributes = changed)
-
attributes.each { |attr| restore_attribute! attr }
-
end
-
-
1
private
-
-
1
def changes_include?(attr_name)
-
1427
attributes_changed_by_setter.include?(attr_name)
-
end
-
1
alias attribute_changed_by_setter? changes_include?
-
-
# Removes current changes and makes them accessible through +previous_changes+.
-
1
def changes_applied # :doc:
-
31
@previously_changed = changes
-
31
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
-
end
-
-
# Clear all dirty data: current changes and previous changes.
-
1
def clear_changes_information # :doc:
-
@previously_changed = ActiveSupport::HashWithIndifferentAccess.new
-
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
-
end
-
-
1
def reset_changes
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
`#reset_changes` is deprecated and will be removed on Rails 5.
-
Please use `#clear_changes_information` instead.
-
MSG
-
-
clear_changes_information
-
end
-
-
# Handle <tt>*_change</tt> for +method_missing+.
-
1
def attribute_change(attr)
-
383
[changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
-
end
-
-
# Handle <tt>*_will_change!</tt> for +method_missing+.
-
1
def attribute_will_change!(attr)
-
return if attribute_changed?(attr)
-
-
begin
-
value = __send__(attr)
-
value = value.duplicable? ? value.clone : value
-
rescue TypeError, NoMethodError
-
end
-
-
set_attribute_was(attr, value)
-
end
-
-
# Handle <tt>reset_*!</tt> for +method_missing+.
-
1
def reset_attribute!(attr)
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
`#reset_#{attr}!` is deprecated and will be removed on Rails 5.
-
Please use `#restore_#{attr}!` instead.
-
MSG
-
-
restore_attribute!(attr)
-
end
-
-
# Handle <tt>restore_*!</tt> for +method_missing+.
-
1
def restore_attribute!(attr)
-
if attribute_changed?(attr)
-
__send__("#{attr}=", changed_attributes[attr])
-
clear_attribute_changes([attr])
-
end
-
end
-
-
# This is necessary because `changed_attributes` might be overridden in
-
# other implemntations (e.g. in `ActiveRecord`)
-
1
alias_method :attributes_changed_by_setter, :changed_attributes # :nodoc:
-
-
# Force an attribute to have a particular "before" value
-
1
def set_attribute_was(attr, old_value)
-
503
attributes_changed_by_setter[attr] = old_value
-
end
-
-
# Remove changes information for the provided attributes.
-
1
def clear_attribute_changes(attributes) # :doc:
-
attributes_changed_by_setter.except!(*attributes)
-
end
-
end
-
end
-
# -*- coding: utf-8 -*-
-
-
1
require 'active_support/core_ext/array/conversions'
-
1
require 'active_support/core_ext/string/inflections'
-
-
1
module ActiveModel
-
# == Active \Model \Errors
-
#
-
# Provides a modified +Hash+ that you can include in your object
-
# for handling error messages and interacting with Action View helpers.
-
#
-
# A minimal implementation could be:
-
#
-
# class Person
-
# # Required dependency for ActiveModel::Errors
-
# extend ActiveModel::Naming
-
#
-
# def initialize
-
# @errors = ActiveModel::Errors.new(self)
-
# end
-
#
-
# attr_accessor :name
-
# attr_reader :errors
-
#
-
# def validate!
-
# errors.add(:name, "cannot be nil") if name.nil?
-
# end
-
#
-
# # The following methods are needed to be minimally implemented
-
#
-
# def read_attribute_for_validation(attr)
-
# send(attr)
-
# end
-
#
-
# def Person.human_attribute_name(attr, options = {})
-
# attr
-
# end
-
#
-
# def Person.lookup_ancestors
-
# [self]
-
# end
-
# end
-
#
-
# The last three methods are required in your object for Errors to be
-
# able to generate error messages correctly and also handle multiple
-
# languages. Of course, if you extend your object with ActiveModel::Translation
-
# you will not need to implement the last two. Likewise, using
-
# ActiveModel::Validations will handle the validation related methods
-
# for you.
-
#
-
# The above allows you to do:
-
#
-
# person = Person.new
-
# person.validate! # => ["cannot be nil"]
-
# person.errors.full_messages # => ["name cannot be nil"]
-
# # etc..
-
1
class Errors
-
1
include Enumerable
-
-
1
CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict]
-
-
1
attr_reader :messages
-
-
# Pass in the instance of the object that is using the errors object.
-
#
-
# class Person
-
# def initialize
-
# @errors = ActiveModel::Errors.new(self)
-
# end
-
# end
-
1
def initialize(base)
-
43
@base = base
-
43
@messages = {}
-
end
-
-
1
def initialize_dup(other) # :nodoc:
-
@messages = other.messages.dup
-
super
-
end
-
-
# Clear the error messages.
-
#
-
# person.errors.full_messages # => ["name cannot be nil"]
-
# person.errors.clear
-
# person.errors.full_messages # => []
-
1
def clear
-
44
messages.clear
-
end
-
-
# Returns +true+ if the error messages include an error for the given key
-
# +attribute+, +false+ otherwise.
-
#
-
# person.errors.messages # => {:name=>["cannot be nil"]}
-
# person.errors.include?(:name) # => true
-
# person.errors.include?(:age) # => false
-
1
def include?(attribute)
-
messages[attribute].present?
-
end
-
# aliases include?
-
1
alias :has_key? :include?
-
# aliases include?
-
1
alias :key? :include?
-
-
# Get messages for +key+.
-
#
-
# person.errors.messages # => {:name=>["cannot be nil"]}
-
# person.errors.get(:name) # => ["cannot be nil"]
-
# person.errors.get(:age) # => nil
-
1
def get(key)
-
40
messages[key]
-
end
-
-
# Set messages for +key+ to +value+.
-
#
-
# person.errors.get(:name) # => ["cannot be nil"]
-
# person.errors.set(:name, ["can't be nil"])
-
# person.errors.get(:name) # => ["can't be nil"]
-
1
def set(key, value)
-
14
messages[key] = value
-
end
-
-
# Delete messages for +key+. Returns the deleted messages.
-
#
-
# person.errors.get(:name) # => ["cannot be nil"]
-
# person.errors.delete(:name) # => ["cannot be nil"]
-
# person.errors.get(:name) # => nil
-
1
def delete(key)
-
messages.delete(key)
-
end
-
-
# When passed a symbol or a name of a method, returns an array of errors
-
# for the method.
-
#
-
# person.errors[:name] # => ["cannot be nil"]
-
# person.errors['name'] # => ["cannot be nil"]
-
1
def [](attribute)
-
40
get(attribute.to_sym) || set(attribute.to_sym, [])
-
end
-
-
# Adds to the supplied attribute the supplied error message.
-
#
-
# person.errors[:name] = "must be set"
-
# person.errors[:name] # => ['must be set']
-
1
def []=(attribute, error)
-
self[attribute] << error
-
end
-
-
# Iterates through each error key, value pair in the error messages hash.
-
# Yields the attribute and the error for that attribute. If the attribute
-
# has more than one error message, yields once for each error message.
-
#
-
# person.errors.add(:name, "can't be blank")
-
# person.errors.each do |attribute, error|
-
# # Will yield :name and "can't be blank"
-
# end
-
#
-
# person.errors.add(:name, "must be specified")
-
# person.errors.each do |attribute, error|
-
# # Will yield :name and "can't be blank"
-
# # then yield :name and "must be specified"
-
# end
-
1
def each
-
88
messages.each_key do |attribute|
-
48
self[attribute].each { |error| yield attribute, error }
-
end
-
end
-
-
# Returns the number of error messages.
-
#
-
# person.errors.add(:name, "can't be blank")
-
# person.errors.size # => 1
-
# person.errors.add(:name, "must be specified")
-
# person.errors.size # => 2
-
1
def size
-
values.flatten.size
-
end
-
-
# Returns all message values.
-
#
-
# person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
-
# person.errors.values # => [["cannot be nil", "must be specified"]]
-
1
def values
-
messages.values
-
end
-
-
# Returns all message keys.
-
#
-
# person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
-
# person.errors.keys # => [:name]
-
1
def keys
-
messages.keys
-
end
-
-
# Returns an array of error messages, with the attribute name included.
-
#
-
# person.errors.add(:name, "can't be blank")
-
# person.errors.add(:name, "must be specified")
-
# person.errors.to_a # => ["name can't be blank", "name must be specified"]
-
1
def to_a
-
full_messages
-
end
-
-
# Returns the number of error messages.
-
#
-
# person.errors.add(:name, "can't be blank")
-
# person.errors.count # => 1
-
# person.errors.add(:name, "must be specified")
-
# person.errors.count # => 2
-
1
def count
-
to_a.size
-
end
-
-
# Returns +true+ if no errors are found, +false+ otherwise.
-
# If the error message is a string it can be empty.
-
#
-
# person.errors.full_messages # => ["name cannot be nil"]
-
# person.errors.empty? # => false
-
1
def empty?
-
112
all? { |k, v| v && v.empty? && !v.is_a?(String) }
-
end
-
# aliases empty?
-
1
alias_method :blank?, :empty?
-
-
# Returns an xml formatted representation of the Errors hash.
-
#
-
# person.errors.add(:name, "can't be blank")
-
# person.errors.add(:name, "must be specified")
-
# person.errors.to_xml
-
# # =>
-
# # <?xml version=\"1.0\" encoding=\"UTF-8\"?>
-
# # <errors>
-
# # <error>name can't be blank</error>
-
# # <error>name must be specified</error>
-
# # </errors>
-
1
def to_xml(options={})
-
to_a.to_xml({ root: "errors", skip_types: true }.merge!(options))
-
end
-
-
# Returns a Hash that can be used as the JSON representation for this
-
# object. You can pass the <tt>:full_messages</tt> option. This determines
-
# if the json object should contain full messages or not (false by default).
-
#
-
# person.errors.as_json # => {:name=>["cannot be nil"]}
-
# person.errors.as_json(full_messages: true) # => {:name=>["name cannot be nil"]}
-
1
def as_json(options=nil)
-
to_hash(options && options[:full_messages])
-
end
-
-
# Returns a Hash of attributes with their error messages. If +full_messages+
-
# is +true+, it will contain full messages (see +full_message+).
-
#
-
# person.errors.to_hash # => {:name=>["cannot be nil"]}
-
# person.errors.to_hash(true) # => {:name=>["name cannot be nil"]}
-
1
def to_hash(full_messages = false)
-
if full_messages
-
self.messages.each_with_object({}) do |(attribute, array), messages|
-
messages[attribute] = array.map { |message| full_message(attribute, message) }
-
end
-
else
-
self.messages.dup
-
end
-
end
-
-
# Adds +message+ to the error messages on +attribute+. More than one error
-
# can be added to the same +attribute+. If no +message+ is supplied,
-
# <tt>:invalid</tt> is assumed.
-
#
-
# person.errors.add(:name)
-
# # => ["is invalid"]
-
# person.errors.add(:name, 'must be implemented')
-
# # => ["is invalid", "must be implemented"]
-
#
-
# person.errors.messages
-
# # => {:name=>["must be implemented", "is invalid"]}
-
#
-
# If +message+ is a symbol, it will be translated using the appropriate
-
# scope (see +generate_message+).
-
#
-
# If +message+ is a proc, it will be called, allowing for things like
-
# <tt>Time.now</tt> to be used within an error.
-
#
-
# If the <tt>:strict</tt> option is set to +true+, it will raise
-
# ActiveModel::StrictValidationFailed instead of adding the error.
-
# <tt>:strict</tt> option can also be set to any other exception.
-
#
-
# person.errors.add(:name, nil, strict: true)
-
# # => ActiveModel::StrictValidationFailed: name is invalid
-
# person.errors.add(:name, nil, strict: NameIsInvalid)
-
# # => NameIsInvalid: name is invalid
-
#
-
# person.errors.messages # => {}
-
#
-
# +attribute+ should be set to <tt>:base</tt> if the error is not
-
# directly associated with a single attribute.
-
#
-
# person.errors.add(:base, "either name or email must be present")
-
# person.errors.messages
-
# # => {:base=>["either name or email must be present"]}
-
1
def add(attribute, message = :invalid, options = {})
-
16
message = normalize_message(attribute, message, options)
-
16
if exception = options[:strict]
-
exception = ActiveModel::StrictValidationFailed if exception == true
-
raise exception, full_message(attribute, message)
-
end
-
-
16
self[attribute] << message
-
end
-
-
# Will add an error message to each of the attributes in +attributes+
-
# that is empty.
-
#
-
# person.errors.add_on_empty(:name)
-
# person.errors.messages
-
# # => {:name=>["can't be empty"]}
-
1
def add_on_empty(attributes, options = {})
-
Array(attributes).each do |attribute|
-
value = @base.send(:read_attribute_for_validation, attribute)
-
is_empty = value.respond_to?(:empty?) ? value.empty? : false
-
add(attribute, :empty, options) if value.nil? || is_empty
-
end
-
end
-
-
# Will add an error message to each of the attributes in +attributes+ that
-
# is blank (using Object#blank?).
-
#
-
# person.errors.add_on_blank(:name)
-
# person.errors.messages
-
# # => {:name=>["can't be blank"]}
-
1
def add_on_blank(attributes, options = {})
-
Array(attributes).each do |attribute|
-
value = @base.send(:read_attribute_for_validation, attribute)
-
add(attribute, :blank, options) if value.blank?
-
end
-
end
-
-
# Returns +true+ if an error on the attribute with the given message is
-
# present, +false+ otherwise. +message+ is treated the same as for +add+.
-
#
-
# person.errors.add :name, :blank
-
# person.errors.added? :name, :blank # => true
-
1
def added?(attribute, message = :invalid, options = {})
-
message = normalize_message(attribute, message, options)
-
self[attribute].include? message
-
end
-
-
# Returns all the full error messages in an array.
-
#
-
# class Person
-
# validates_presence_of :name, :address, :email
-
# validates_length_of :name, in: 5..30
-
# end
-
#
-
# person = Person.create(address: '123 First St.')
-
# person.errors.full_messages
-
# # => ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Email can't be blank"]
-
1
def full_messages
-
map { |attribute, message| full_message(attribute, message) }
-
end
-
-
# Returns all the full error messages for a given attribute in an array.
-
#
-
# class Person
-
# validates_presence_of :name, :email
-
# validates_length_of :name, in: 5..30
-
# end
-
#
-
# person = Person.create()
-
# person.errors.full_messages_for(:name)
-
# # => ["Name is too short (minimum is 5 characters)", "Name can't be blank"]
-
1
def full_messages_for(attribute)
-
(get(attribute) || []).map { |message| full_message(attribute, message) }
-
end
-
-
# Returns a full message for a given attribute.
-
#
-
# person.errors.full_message(:name, 'is invalid') # => "Name is invalid"
-
1
def full_message(attribute, message)
-
return message if attribute == :base
-
attr_name = attribute.to_s.tr('.', '_').humanize
-
attr_name = @base.class.human_attribute_name(attribute, default: attr_name)
-
I18n.t(:"errors.format", {
-
default: "%{attribute} %{message}",
-
attribute: attr_name,
-
message: message
-
})
-
end
-
-
# Translates an error message in its default scope
-
# (<tt>activemodel.errors.messages</tt>).
-
#
-
# Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>,
-
# if it's not there, it's looked up in <tt>models.MODEL.MESSAGE</tt> and if
-
# that is not there also, it returns the translation of the default message
-
# (e.g. <tt>activemodel.errors.messages.MESSAGE</tt>). The translated model
-
# name, translated attribute name and the value are available for
-
# interpolation.
-
#
-
# When using inheritance in your models, it will check all the inherited
-
# models too, but only if the model itself hasn't been found. Say you have
-
# <tt>class Admin < User; end</tt> and you wanted the translation for
-
# the <tt>:blank</tt> error message for the <tt>title</tt> attribute,
-
# it looks for these translations:
-
#
-
# * <tt>activemodel.errors.models.admin.attributes.title.blank</tt>
-
# * <tt>activemodel.errors.models.admin.blank</tt>
-
# * <tt>activemodel.errors.models.user.attributes.title.blank</tt>
-
# * <tt>activemodel.errors.models.user.blank</tt>
-
# * any default you provided through the +options+ hash (in the <tt>activemodel.errors</tt> scope)
-
# * <tt>activemodel.errors.messages.blank</tt>
-
# * <tt>errors.attributes.title.blank</tt>
-
# * <tt>errors.messages.blank</tt>
-
1
def generate_message(attribute, type = :invalid, options = {})
-
16
type = options.delete(:message) if options[:message].is_a?(Symbol)
-
-
16
if @base.class.respond_to?(:i18n_scope)
-
16
defaults = @base.class.lookup_ancestors.map do |klass|
-
16
[ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}",
-
:"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ]
-
end
-
else
-
defaults = []
-
end
-
-
16
defaults << options.delete(:message)
-
16
defaults << :"#{@base.class.i18n_scope}.errors.messages.#{type}" if @base.class.respond_to?(:i18n_scope)
-
16
defaults << :"errors.attributes.#{attribute}.#{type}"
-
16
defaults << :"errors.messages.#{type}"
-
-
16
defaults.compact!
-
16
defaults.flatten!
-
-
16
key = defaults.shift
-
16
value = (attribute != :base ? @base.send(:read_attribute_for_validation, attribute) : nil)
-
-
16
options = {
-
default: defaults,
-
model: @base.model_name.human,
-
attribute: @base.class.human_attribute_name(attribute),
-
value: value
-
}.merge!(options)
-
-
16
I18n.translate(key, options)
-
end
-
-
1
private
-
1
def normalize_message(attribute, message, options)
-
16
case message
-
when Symbol
-
16
generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS))
-
when Proc
-
message.call
-
else
-
message
-
end
-
end
-
end
-
-
# Raised when a validation cannot be corrected by end users and are considered
-
# exceptional.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :name
-
#
-
# validates_presence_of :name, strict: true
-
# end
-
#
-
# person = Person.new
-
# person.name = nil
-
# person.valid?
-
# # => ActiveModel::StrictValidationFailed: Name can't be blank
-
1
class StrictValidationFailed < StandardError
-
end
-
end
-
1
module ActiveModel
-
# Raised when forbidden attributes are used for mass assignment.
-
#
-
# class Person < ActiveRecord::Base
-
# end
-
#
-
# params = ActionController::Parameters.new(name: 'Bob')
-
# Person.new(params)
-
# # => ActiveModel::ForbiddenAttributesError
-
#
-
# params.permit!
-
# Person.new(params)
-
# # => #<Person id: nil, name: "Bob">
-
1
class ForbiddenAttributesError < StandardError
-
end
-
-
1
module ForbiddenAttributesProtection # :nodoc:
-
1
protected
-
1
def sanitize_for_mass_assignment(attributes)
-
72
if attributes.respond_to?(:permitted?) && !attributes.permitted?
-
raise ActiveModel::ForbiddenAttributesError
-
else
-
72
attributes
-
end
-
end
-
1
alias :sanitize_forbidden_attributes :sanitize_for_mass_assignment
-
end
-
end
-
1
require 'active_support/core_ext/hash/except'
-
1
require 'active_support/core_ext/module/introspection'
-
1
require 'active_support/core_ext/module/remove_method'
-
-
1
module ActiveModel
-
1
class Name
-
1
include Comparable
-
-
1
attr_reader :singular, :plural, :element, :collection,
-
:singular_route_key, :route_key, :param_key, :i18n_key,
-
:name
-
-
1
alias_method :cache_key, :collection
-
-
##
-
# :method: ==
-
#
-
# :call-seq:
-
# ==(other)
-
#
-
# Equivalent to <tt>String#==</tt>. Returns +true+ if the class name and
-
# +other+ are equal, otherwise +false+.
-
#
-
# class BlogPost
-
# extend ActiveModel::Naming
-
# end
-
#
-
# BlogPost.model_name == 'BlogPost' # => true
-
# BlogPost.model_name == 'Blog Post' # => false
-
-
##
-
# :method: ===
-
#
-
# :call-seq:
-
# ===(other)
-
#
-
# Equivalent to <tt>#==</tt>.
-
#
-
# class BlogPost
-
# extend ActiveModel::Naming
-
# end
-
#
-
# BlogPost.model_name === 'BlogPost' # => true
-
# BlogPost.model_name === 'Blog Post' # => false
-
-
##
-
# :method: <=>
-
#
-
# :call-seq:
-
# ==(other)
-
#
-
# Equivalent to <tt>String#<=></tt>.
-
#
-
# class BlogPost
-
# extend ActiveModel::Naming
-
# end
-
#
-
# BlogPost.model_name <=> 'BlogPost' # => 0
-
# BlogPost.model_name <=> 'Blog' # => 1
-
# BlogPost.model_name <=> 'BlogPosts' # => -1
-
-
##
-
# :method: =~
-
#
-
# :call-seq:
-
# =~(regexp)
-
#
-
# Equivalent to <tt>String#=~</tt>. Match the class name against the given
-
# regexp. Returns the position where the match starts or +nil+ if there is
-
# no match.
-
#
-
# class BlogPost
-
# extend ActiveModel::Naming
-
# end
-
#
-
# BlogPost.model_name =~ /Post/ # => 4
-
# BlogPost.model_name =~ /\d/ # => nil
-
-
##
-
# :method: !~
-
#
-
# :call-seq:
-
# !~(regexp)
-
#
-
# Equivalent to <tt>String#!~</tt>. Match the class name against the given
-
# regexp. Returns +true+ if there is no match, otherwise +false+.
-
#
-
# class BlogPost
-
# extend ActiveModel::Naming
-
# end
-
#
-
# BlogPost.model_name !~ /Post/ # => false
-
# BlogPost.model_name !~ /\d/ # => true
-
-
##
-
# :method: eql?
-
#
-
# :call-seq:
-
# eql?(other)
-
#
-
# Equivalent to <tt>String#eql?</tt>. Returns +true+ if the class name and
-
# +other+ have the same length and content, otherwise +false+.
-
#
-
# class BlogPost
-
# extend ActiveModel::Naming
-
# end
-
#
-
# BlogPost.model_name.eql?('BlogPost') # => true
-
# BlogPost.model_name.eql?('Blog Post') # => false
-
-
##
-
# :method: to_s
-
#
-
# :call-seq:
-
# to_s()
-
#
-
# Returns the class name.
-
#
-
# class BlogPost
-
# extend ActiveModel::Naming
-
# end
-
#
-
# BlogPost.model_name.to_s # => "BlogPost"
-
-
##
-
# :method: to_str
-
#
-
# :call-seq:
-
# to_str()
-
#
-
# Equivalent to +to_s+.
-
1
delegate :==, :===, :<=>, :=~, :"!~", :eql?, :to_s,
-
:to_str, :as_json, to: :name
-
-
# Returns a new ActiveModel::Name instance. By default, the +namespace+
-
# and +name+ option will take the namespace and name of the given class
-
# respectively.
-
#
-
# module Foo
-
# class Bar
-
# end
-
# end
-
#
-
# ActiveModel::Name.new(Foo::Bar).to_s
-
# # => "Foo::Bar"
-
1
def initialize(klass, namespace = nil, name = nil)
-
2
@name = name || klass.name
-
-
2
raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if @name.blank?
-
-
2
@unnamespaced = @name.sub(/^#{namespace.name}::/, '') if namespace
-
2
@klass = klass
-
2
@singular = _singularize(@name)
-
2
@plural = ActiveSupport::Inflector.pluralize(@singular)
-
2
@element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(@name))
-
2
@human = ActiveSupport::Inflector.humanize(@element)
-
2
@collection = ActiveSupport::Inflector.tableize(@name)
-
2
@param_key = (namespace ? _singularize(@unnamespaced) : @singular)
-
2
@i18n_key = @name.underscore.to_sym
-
-
2
@route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural.dup)
-
2
@singular_route_key = ActiveSupport::Inflector.singularize(@route_key)
-
2
@route_key << "_index" if @plural == @singular
-
end
-
-
# Transform the model name into a more humane format, using I18n. By default,
-
# it will underscore then humanize the class name.
-
#
-
# class BlogPost
-
# extend ActiveModel::Naming
-
# end
-
#
-
# BlogPost.model_name.human # => "Blog post"
-
#
-
# Specify +options+ with additional translating options.
-
1
def human(options={})
-
return @human unless @klass.respond_to?(:lookup_ancestors) &&
-
16
@klass.respond_to?(:i18n_scope)
-
-
16
defaults = @klass.lookup_ancestors.map do |klass|
-
16
klass.model_name.i18n_key
-
end
-
-
16
defaults << options[:default] if options[:default]
-
16
defaults << @human
-
-
16
options = { scope: [@klass.i18n_scope, :models], count: 1, default: defaults }.merge!(options.except(:default))
-
16
I18n.translate(defaults.shift, options)
-
end
-
-
1
private
-
-
1
def _singularize(string, replacement='_')
-
2
ActiveSupport::Inflector.underscore(string).tr('/', replacement)
-
end
-
end
-
-
# == Active \Model \Naming
-
#
-
# Creates a +model_name+ method on your object.
-
#
-
# To implement, just extend ActiveModel::Naming in your object:
-
#
-
# class BookCover
-
# extend ActiveModel::Naming
-
# end
-
#
-
# BookCover.model_name.name # => "BookCover"
-
# BookCover.model_name.human # => "Book cover"
-
#
-
# BookCover.model_name.i18n_key # => :book_cover
-
# BookModule::BookCover.model_name.i18n_key # => :"book_module/book_cover"
-
#
-
# Providing the functionality that ActiveModel::Naming provides in your object
-
# is required to pass the Active Model Lint test. So either extending the
-
# provided method below, or rolling your own is required.
-
1
module Naming
-
1
def self.extended(base) #:nodoc:
-
4
base.remove_possible_method :model_name
-
4
base.delegate :model_name, to: :class
-
end
-
-
# Returns an ActiveModel::Name object for module. It can be
-
# used to retrieve all kinds of naming-related information
-
# (See ActiveModel::Name for more information).
-
#
-
# class Person
-
# extend ActiveModel::Naming
-
# end
-
#
-
# Person.model_name.name # => "Person"
-
# Person.model_name.class # => ActiveModel::Name
-
# Person.model_name.singular # => "person"
-
# Person.model_name.plural # => "people"
-
1
def model_name
-
@_model_name ||= begin
-
2
namespace = self.parents.detect do |n|
-
2
n.respond_to?(:use_relative_model_naming?) && n.use_relative_model_naming?
-
end
-
2
ActiveModel::Name.new(self, namespace)
-
88
end
-
end
-
-
# Returns the plural class name of a record or class.
-
#
-
# ActiveModel::Naming.plural(post) # => "posts"
-
# ActiveModel::Naming.plural(Highrise::Person) # => "highrise_people"
-
1
def self.plural(record_or_class)
-
model_name_from_record_or_class(record_or_class).plural
-
end
-
-
# Returns the singular class name of a record or class.
-
#
-
# ActiveModel::Naming.singular(post) # => "post"
-
# ActiveModel::Naming.singular(Highrise::Person) # => "highrise_person"
-
1
def self.singular(record_or_class)
-
model_name_from_record_or_class(record_or_class).singular
-
end
-
-
# Identifies whether the class name of a record or class is uncountable.
-
#
-
# ActiveModel::Naming.uncountable?(Sheep) # => true
-
# ActiveModel::Naming.uncountable?(Post) # => false
-
1
def self.uncountable?(record_or_class)
-
plural(record_or_class) == singular(record_or_class)
-
end
-
-
# Returns string to use while generating route names. It differs for
-
# namespaced models regarding whether it's inside isolated engine.
-
#
-
# # For isolated engine:
-
# ActiveModel::Naming.singular_route_key(Blog::Post) # => "post"
-
#
-
# # For shared engine:
-
# ActiveModel::Naming.singular_route_key(Blog::Post) # => "blog_post"
-
1
def self.singular_route_key(record_or_class)
-
model_name_from_record_or_class(record_or_class).singular_route_key
-
end
-
-
# Returns string to use while generating route names. It differs for
-
# namespaced models regarding whether it's inside isolated engine.
-
#
-
# # For isolated engine:
-
# ActiveModel::Naming.route_key(Blog::Post) # => "posts"
-
#
-
# # For shared engine:
-
# ActiveModel::Naming.route_key(Blog::Post) # => "blog_posts"
-
#
-
# The route key also considers if the noun is uncountable and, in
-
# such cases, automatically appends _index.
-
1
def self.route_key(record_or_class)
-
model_name_from_record_or_class(record_or_class).route_key
-
end
-
-
# Returns string to use for params names. It differs for
-
# namespaced models regarding whether it's inside isolated engine.
-
#
-
# # For isolated engine:
-
# ActiveModel::Naming.param_key(Blog::Post) # => "post"
-
#
-
# # For shared engine:
-
# ActiveModel::Naming.param_key(Blog::Post) # => "blog_post"
-
1
def self.param_key(record_or_class)
-
model_name_from_record_or_class(record_or_class).param_key
-
end
-
-
1
def self.model_name_from_record_or_class(record_or_class) #:nodoc:
-
if record_or_class.respond_to?(:to_model)
-
record_or_class.to_model.model_name
-
else
-
record_or_class.model_name
-
end
-
end
-
1
private_class_method :model_name_from_record_or_class
-
end
-
end
-
1
require 'active_support/core_ext/hash/except'
-
1
require 'active_support/core_ext/hash/slice'
-
-
1
module ActiveModel
-
# == Active \Model \Serialization
-
#
-
# Provides a basic serialization to a serializable_hash for your objects.
-
#
-
# A minimal implementation could be:
-
#
-
# class Person
-
# include ActiveModel::Serialization
-
#
-
# attr_accessor :name
-
#
-
# def attributes
-
# {'name' => nil}
-
# end
-
# end
-
#
-
# Which would provide you with:
-
#
-
# person = Person.new
-
# person.serializable_hash # => {"name"=>nil}
-
# person.name = "Bob"
-
# person.serializable_hash # => {"name"=>"Bob"}
-
#
-
# An +attributes+ hash must be defined and should contain any attributes you
-
# need to be serialized. Attributes must be strings, not symbols.
-
# When called, serializable hash will use instance methods that match the name
-
# of the attributes hash's keys. In order to override this behavior, take a look
-
# at the private method +read_attribute_for_serialization+.
-
#
-
# Most of the time though, either the JSON or XML serializations are needed.
-
# Both of these modules automatically include the
-
# <tt>ActiveModel::Serialization</tt> module, so there is no need to
-
# explicitly include it.
-
#
-
# A minimal implementation including XML and JSON would be:
-
#
-
# class Person
-
# include ActiveModel::Serializers::JSON
-
# include ActiveModel::Serializers::Xml
-
#
-
# attr_accessor :name
-
#
-
# def attributes
-
# {'name' => nil}
-
# end
-
# end
-
#
-
# Which would provide you with:
-
#
-
# person = Person.new
-
# person.serializable_hash # => {"name"=>nil}
-
# person.as_json # => {"name"=>nil}
-
# person.to_json # => "{\"name\":null}"
-
# person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
-
#
-
# person.name = "Bob"
-
# person.serializable_hash # => {"name"=>"Bob"}
-
# person.as_json # => {"name"=>"Bob"}
-
# person.to_json # => "{\"name\":\"Bob\"}"
-
# person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
-
#
-
# Valid options are <tt>:only</tt>, <tt>:except</tt>, <tt>:methods</tt> and
-
# <tt>:include</tt>. The following are all valid examples:
-
#
-
# person.serializable_hash(only: 'name')
-
# person.serializable_hash(include: :address)
-
# person.serializable_hash(include: { address: { only: 'city' }})
-
1
module Serialization
-
# Returns a serialized hash of your object.
-
#
-
# class Person
-
# include ActiveModel::Serialization
-
#
-
# attr_accessor :name, :age
-
#
-
# def attributes
-
# {'name' => nil, 'age' => nil}
-
# end
-
#
-
# def capitalized_name
-
# name.capitalize
-
# end
-
# end
-
#
-
# person = Person.new
-
# person.name = 'bob'
-
# person.age = 22
-
# person.serializable_hash # => {"name"=>"bob", "age"=>22}
-
# person.serializable_hash(only: :name) # => {"name"=>"bob"}
-
# person.serializable_hash(except: :name) # => {"age"=>22}
-
# person.serializable_hash(methods: :capitalized_name)
-
# # => {"name"=>"bob", "age"=>22, "capitalized_name"=>"Bob"}
-
1
def serializable_hash(options = nil)
-
options ||= {}
-
-
attribute_names = attributes.keys
-
if only = options[:only]
-
attribute_names &= Array(only).map(&:to_s)
-
elsif except = options[:except]
-
attribute_names -= Array(except).map(&:to_s)
-
end
-
-
hash = {}
-
attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) }
-
-
Array(options[:methods]).each { |m| hash[m.to_s] = send(m) if respond_to?(m) }
-
-
serializable_add_includes(options) do |association, records, opts|
-
hash[association.to_s] = if records.respond_to?(:to_ary)
-
records.to_ary.map { |a| a.serializable_hash(opts) }
-
else
-
records.serializable_hash(opts)
-
end
-
end
-
-
hash
-
end
-
-
1
private
-
-
# Hook method defining how an attribute value should be retrieved for
-
# serialization. By default this is assumed to be an instance named after
-
# the attribute. Override this method in subclasses should you need to
-
# retrieve the value for a given attribute differently:
-
#
-
# class MyClass
-
# include ActiveModel::Serialization
-
#
-
# def initialize(data = {})
-
# @data = data
-
# end
-
#
-
# def read_attribute_for_serialization(key)
-
# @data[key]
-
# end
-
# end
-
1
alias :read_attribute_for_serialization :send
-
-
# Add associations specified via the <tt>:include</tt> option.
-
#
-
# Expects a block that takes as arguments:
-
# +association+ - name of the association
-
# +records+ - the association record(s) to be serialized
-
# +opts+ - options for the association records
-
1
def serializable_add_includes(options = {}) #:nodoc:
-
return unless includes = options[:include]
-
-
unless includes.is_a?(Hash)
-
includes = Hash[Array(includes).map { |n| n.is_a?(Hash) ? n.to_a.first : [n, {}] }]
-
end
-
-
includes.each do |association, opts|
-
if records = send(association)
-
yield association, records, opts
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/json'
-
-
1
module ActiveModel
-
1
module Serializers
-
# == Active \Model \JSON \Serializer
-
1
module JSON
-
1
extend ActiveSupport::Concern
-
1
include ActiveModel::Serialization
-
-
1
included do
-
1
extend ActiveModel::Naming
-
-
1
class_attribute :include_root_in_json
-
1
self.include_root_in_json = false
-
end
-
-
# Returns a hash representing the model. Some configuration can be
-
# passed through +options+.
-
#
-
# The option <tt>include_root_in_json</tt> controls the top-level behavior
-
# of +as_json+. If +true+, +as_json+ will emit a single root node named
-
# after the object's type. The default value for <tt>include_root_in_json</tt>
-
# option is +false+.
-
#
-
# user = User.find(1)
-
# user.as_json
-
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
-
# # "created_at" => "2006/08/01", "awesome" => true}
-
#
-
# ActiveRecord::Base.include_root_in_json = true
-
#
-
# user.as_json
-
# # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
-
# # "created_at" => "2006/08/01", "awesome" => true } }
-
#
-
# This behavior can also be achieved by setting the <tt>:root</tt> option
-
# to +true+ as in:
-
#
-
# user = User.find(1)
-
# user.as_json(root: true)
-
# # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
-
# # "created_at" => "2006/08/01", "awesome" => true } }
-
#
-
# Without any +options+, the returned Hash will include all the model's
-
# attributes.
-
#
-
# user = User.find(1)
-
# user.as_json
-
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
-
# # "created_at" => "2006/08/01", "awesome" => true}
-
#
-
# The <tt>:only</tt> and <tt>:except</tt> options can be used to limit
-
# the attributes included, and work similar to the +attributes+ method.
-
#
-
# user.as_json(only: [:id, :name])
-
# # => { "id" => 1, "name" => "Konata Izumi" }
-
#
-
# user.as_json(except: [:id, :created_at, :age])
-
# # => { "name" => "Konata Izumi", "awesome" => true }
-
#
-
# To include the result of some method calls on the model use <tt>:methods</tt>:
-
#
-
# user.as_json(methods: :permalink)
-
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
-
# # "created_at" => "2006/08/01", "awesome" => true,
-
# # "permalink" => "1-konata-izumi" }
-
#
-
# To include associations use <tt>:include</tt>:
-
#
-
# user.as_json(include: :posts)
-
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
-
# # "created_at" => "2006/08/01", "awesome" => true,
-
# # "posts" => [ { "id" => 1, "author_id" => 1, "title" => "Welcome to the weblog" },
-
# # { "id" => 2, "author_id" => 1, "title" => "So I was thinking" } ] }
-
#
-
# Second level and higher order associations work as well:
-
#
-
# user.as_json(include: { posts: {
-
# include: { comments: {
-
# only: :body } },
-
# only: :title } })
-
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
-
# # "created_at" => "2006/08/01", "awesome" => true,
-
# # "posts" => [ { "comments" => [ { "body" => "1st post!" }, { "body" => "Second!" } ],
-
# # "title" => "Welcome to the weblog" },
-
# # { "comments" => [ { "body" => "Don't think too hard" } ],
-
# # "title" => "So I was thinking" } ] }
-
1
def as_json(options = nil)
-
root = if options && options.key?(:root)
-
options[:root]
-
else
-
include_root_in_json
-
end
-
-
if root
-
root = model_name.element if root == true
-
{ root => serializable_hash(options) }
-
else
-
serializable_hash(options)
-
end
-
end
-
-
# Sets the model +attributes+ from a JSON string. Returns +self+.
-
#
-
# class Person
-
# include ActiveModel::Serializers::JSON
-
#
-
# attr_accessor :name, :age, :awesome
-
#
-
# def attributes=(hash)
-
# hash.each do |key, value|
-
# send("#{key}=", value)
-
# end
-
# end
-
#
-
# def attributes
-
# instance_values
-
# end
-
# end
-
#
-
# json = { name: 'bob', age: 22, awesome:true }.to_json
-
# person = Person.new
-
# person.from_json(json) # => #<Person:0x007fec5e7a0088 @age=22, @awesome=true, @name="bob">
-
# person.name # => "bob"
-
# person.age # => 22
-
# person.awesome # => true
-
#
-
# The default value for +include_root+ is +false+. You can change it to
-
# +true+ if the given JSON string includes a single root node.
-
#
-
# json = { person: { name: 'bob', age: 22, awesome:true } }.to_json
-
# person = Person.new
-
# person.from_json(json, true) # => #<Person:0x007fec5e7a0088 @age=22, @awesome=true, @name="bob">
-
# person.name # => "bob"
-
# person.age # => 22
-
# person.awesome # => true
-
1
def from_json(json, include_root=include_root_in_json)
-
hash = ActiveSupport::JSON.decode(json)
-
hash = hash.values.first if include_root
-
self.attributes = hash
-
self
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/module/attribute_accessors'
-
1
require 'active_support/core_ext/array/conversions'
-
1
require 'active_support/core_ext/hash/conversions'
-
1
require 'active_support/core_ext/hash/slice'
-
1
require 'active_support/core_ext/time/acts_like'
-
-
1
module ActiveModel
-
1
module Serializers
-
# == Active Model XML Serializer
-
1
module Xml
-
1
extend ActiveSupport::Concern
-
1
include ActiveModel::Serialization
-
-
1
included do
-
1
extend ActiveModel::Naming
-
end
-
-
1
class Serializer #:nodoc:
-
1
class Attribute #:nodoc:
-
1
attr_reader :name, :value, :type
-
-
1
def initialize(name, serializable, value)
-
@name, @serializable = name, serializable
-
-
if value.acts_like?(:time) && value.respond_to?(:in_time_zone)
-
value = value.in_time_zone
-
end
-
-
@value = value
-
@type = compute_type
-
end
-
-
1
def decorations
-
decorations = {}
-
decorations[:encoding] = 'base64' if type == :binary
-
decorations[:type] = (type == :string) ? nil : type
-
decorations[:nil] = true if value.nil?
-
decorations
-
end
-
-
1
protected
-
-
1
def compute_type
-
return if value.nil?
-
type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name]
-
type ||= :string if value.respond_to?(:to_str)
-
type ||= :yaml
-
type
-
end
-
end
-
-
1
class MethodAttribute < Attribute #:nodoc:
-
end
-
-
1
attr_reader :options
-
-
1
def initialize(serializable, options = nil)
-
@serializable = serializable
-
@options = options ? options.dup : {}
-
end
-
-
1
def serializable_hash
-
@serializable.serializable_hash(@options.except(:include))
-
end
-
-
1
def serializable_collection
-
methods = Array(options[:methods]).map(&:to_s)
-
serializable_hash.map do |name, value|
-
name = name.to_s
-
if methods.include?(name)
-
self.class::MethodAttribute.new(name, @serializable, value)
-
else
-
self.class::Attribute.new(name, @serializable, value)
-
end
-
end
-
end
-
-
1
def serialize
-
require 'builder' unless defined? ::Builder
-
-
options[:indent] ||= 2
-
options[:builder] ||= ::Builder::XmlMarkup.new(indent: options[:indent])
-
-
@builder = options[:builder]
-
@builder.instruct! unless options[:skip_instruct]
-
-
root = (options[:root] || @serializable.model_name.element).to_s
-
root = ActiveSupport::XmlMini.rename_key(root, options)
-
-
args = [root]
-
args << { xmlns: options[:namespace] } if options[:namespace]
-
args << { type: options[:type] } if options[:type] && !options[:skip_types]
-
-
@builder.tag!(*args) do
-
add_attributes_and_methods
-
add_includes
-
add_extra_behavior
-
add_procs
-
yield @builder if block_given?
-
end
-
end
-
-
1
private
-
-
1
def add_extra_behavior
-
end
-
-
1
def add_attributes_and_methods
-
serializable_collection.each do |attribute|
-
key = ActiveSupport::XmlMini.rename_key(attribute.name, options)
-
ActiveSupport::XmlMini.to_tag(key, attribute.value,
-
options.merge(attribute.decorations))
-
end
-
end
-
-
1
def add_includes
-
@serializable.send(:serializable_add_includes, options) do |association, records, opts|
-
add_associations(association, records, opts)
-
end
-
end
-
-
# TODO: This can likely be cleaned up to simple use ActiveSupport::XmlMini.to_tag as well.
-
1
def add_associations(association, records, opts)
-
merged_options = opts.merge(options.slice(:builder, :indent))
-
merged_options[:skip_instruct] = true
-
-
[:skip_types, :dasherize, :camelize].each do |key|
-
merged_options[key] = options[key] if merged_options[key].nil? && !options[key].nil?
-
end
-
-
if records.respond_to?(:to_ary)
-
records = records.to_ary
-
-
tag = ActiveSupport::XmlMini.rename_key(association.to_s, options)
-
type = options[:skip_types] ? { } : { type: "array" }
-
association_name = association.to_s.singularize
-
merged_options[:root] = association_name
-
-
if records.empty?
-
@builder.tag!(tag, type)
-
else
-
@builder.tag!(tag, type) do
-
records.each do |record|
-
if options[:skip_types]
-
record_type = {}
-
else
-
record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name
-
record_type = { type: record_class }
-
end
-
-
record.to_xml merged_options.merge(record_type)
-
end
-
end
-
end
-
else
-
merged_options[:root] = association.to_s
-
-
unless records.class.to_s.underscore == association.to_s
-
merged_options[:type] = records.class.name
-
end
-
-
records.to_xml merged_options
-
end
-
end
-
-
1
def add_procs
-
if procs = options.delete(:procs)
-
Array(procs).each do |proc|
-
if proc.arity == 1
-
proc.call(options)
-
else
-
proc.call(options, @serializable)
-
end
-
end
-
end
-
end
-
end
-
-
# Returns XML representing the model. Configuration can be
-
# passed through +options+.
-
#
-
# Without any +options+, the returned XML string will include all the
-
# model's attributes.
-
#
-
# user = User.find(1)
-
# user.to_xml
-
#
-
# <?xml version="1.0" encoding="UTF-8"?>
-
# <user>
-
# <id type="integer">1</id>
-
# <name>David</name>
-
# <age type="integer">16</age>
-
# <created-at type="dateTime">2011-01-30T22:29:23Z</created-at>
-
# </user>
-
#
-
# The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the
-
# attributes included, and work similar to the +attributes+ method.
-
#
-
# To include the result of some method calls on the model use <tt>:methods</tt>.
-
#
-
# To include associations use <tt>:include</tt>.
-
#
-
# For further documentation, see <tt>ActiveRecord::Serialization#to_xml</tt>
-
1
def to_xml(options = {}, &block)
-
Serializer.new(self, options).serialize(&block)
-
end
-
-
# Sets the model +attributes+ from an XML string. Returns +self+.
-
#
-
# class Person
-
# include ActiveModel::Serializers::Xml
-
#
-
# attr_accessor :name, :age, :awesome
-
#
-
# def attributes=(hash)
-
# hash.each do |key, value|
-
# instance_variable_set("@#{key}", value)
-
# end
-
# end
-
#
-
# def attributes
-
# instance_values
-
# end
-
# end
-
#
-
# xml = { name: 'bob', age: 22, awesome:true }.to_xml
-
# person = Person.new
-
# person.from_xml(xml) # => #<Person:0x007fec5e3b3c40 @age=22, @awesome=true, @name="bob">
-
# person.name # => "bob"
-
# person.age # => 22
-
# person.awesome # => true
-
1
def from_xml(xml)
-
self.attributes = Hash.from_xml(xml).values.first
-
self
-
end
-
end
-
end
-
end
-
1
module ActiveModel
-
-
# == Active \Model \Translation
-
#
-
# Provides integration between your object and the Rails internationalization
-
# (i18n) framework.
-
#
-
# A minimal implementation could be:
-
#
-
# class TranslatedPerson
-
# extend ActiveModel::Translation
-
# end
-
#
-
# TranslatedPerson.human_attribute_name('my_attribute')
-
# # => "My attribute"
-
#
-
# This also provides the required class methods for hooking into the
-
# Rails internationalization API, including being able to define a
-
# class based +i18n_scope+ and +lookup_ancestors+ to find translations in
-
# parent classes.
-
1
module Translation
-
1
include ActiveModel::Naming
-
-
# Returns the +i18n_scope+ for the class. Overwrite if you want custom lookup.
-
1
def i18n_scope
-
:activemodel
-
end
-
-
# When localizing a string, it goes through the lookup returned by this
-
# method, which is used in ActiveModel::Name#human,
-
# ActiveModel::Errors#full_messages and
-
# ActiveModel::Translation#human_attribute_name.
-
1
def lookup_ancestors
-
self.ancestors.select { |x| x.respond_to?(:model_name) }
-
end
-
-
# Transforms attribute names into a more human format, such as "First name"
-
# instead of "first_name".
-
#
-
# Person.human_attribute_name("first_name") # => "First name"
-
#
-
# Specify +options+ with additional translating options.
-
1
def human_attribute_name(attribute, options = {})
-
18
options = { count: 1 }.merge!(options)
-
18
parts = attribute.to_s.split(".")
-
18
attribute = parts.pop
-
18
namespace = parts.join("/") unless parts.empty?
-
18
attributes_scope = "#{self.i18n_scope}.attributes"
-
-
18
if namespace
-
defaults = lookup_ancestors.map do |klass|
-
:"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.#{attribute}"
-
end
-
defaults << :"#{attributes_scope}.#{namespace}.#{attribute}"
-
else
-
18
defaults = lookup_ancestors.map do |klass|
-
18
:"#{attributes_scope}.#{klass.model_name.i18n_key}.#{attribute}"
-
end
-
end
-
-
18
defaults << :"attributes.#{attribute}"
-
18
defaults << options.delete(:default) if options[:default]
-
18
defaults << attribute.humanize
-
-
18
options[:default] = defaults
-
18
I18n.translate(defaults.shift, options)
-
end
-
end
-
end
-
1
require 'active_support/core_ext/array/extract_options'
-
1
require 'active_support/core_ext/hash/keys'
-
1
require 'active_support/core_ext/hash/except'
-
-
1
module ActiveModel
-
-
# == Active \Model \Validations
-
#
-
# Provides a full validation framework to your objects.
-
#
-
# A minimal implementation could be:
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :first_name, :last_name
-
#
-
# validates_each :first_name, :last_name do |record, attr, value|
-
# record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
-
# end
-
# end
-
#
-
# Which provides you with the full standard validation stack that you
-
# know from Active Record:
-
#
-
# person = Person.new
-
# person.valid? # => true
-
# person.invalid? # => false
-
#
-
# person.first_name = 'zoolander'
-
# person.valid? # => false
-
# person.invalid? # => true
-
# person.errors.messages # => {first_name:["starts with z."]}
-
#
-
# Note that <tt>ActiveModel::Validations</tt> automatically adds an +errors+
-
# method to your instances initialized with a new <tt>ActiveModel::Errors</tt>
-
# object, so there is no need for you to do this manually.
-
1
module Validations
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
extend ActiveModel::Naming
-
1
extend ActiveModel::Callbacks
-
1
extend ActiveModel::Translation
-
-
1
extend HelperMethods
-
1
include HelperMethods
-
-
1
attr_accessor :validation_context
-
1
define_callbacks :validate, scope: :name
-
-
1
class_attribute :_validators
-
6
self._validators = Hash.new { |h,k| h[k] = [] }
-
end
-
-
1
module ClassMethods
-
# Validates each attribute against a block.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :first_name, :last_name
-
#
-
# validates_each :first_name, :last_name, allow_blank: true do |record, attr, value|
-
# record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
-
# end
-
# end
-
#
-
# Options:
-
# * <tt>:on</tt> - Specifies the contexts where this validation is active.
-
# Runs in all validation contexts by default (nil). You can pass a symbol
-
# or an array of symbols. (e.g. <tt>on: :create</tt> or
-
# <tt>on: :custom_validation_context</tt> or
-
# <tt>on: [:create, :custom_validation_context]</tt>)
-
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
-
# * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
-
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
-
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
-
# proc or string should return or evaluate to a +true+ or +false+ value.
-
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
-
# determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
-
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
-
# method, proc or string should return or evaluate to a +true+ or +false+
-
# value.
-
1
def validates_each(*attr_names, &block)
-
validates_with BlockValidator, _merge_attributes(attr_names), &block
-
end
-
-
1
VALID_OPTIONS_FOR_VALIDATE = [:on, :if, :unless, :prepend].freeze # :nodoc:
-
-
# Adds a validation method or block to the class. This is useful when
-
# overriding the +validate+ instance method becomes too unwieldy and
-
# you're looking for more descriptive declaration of your validations.
-
#
-
# This can be done with a symbol pointing to a method:
-
#
-
# class Comment
-
# include ActiveModel::Validations
-
#
-
# validate :must_be_friends
-
#
-
# def must_be_friends
-
# errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
-
# end
-
# end
-
#
-
# With a block which is passed with the current record to be validated:
-
#
-
# class Comment
-
# include ActiveModel::Validations
-
#
-
# validate do |comment|
-
# comment.must_be_friends
-
# end
-
#
-
# def must_be_friends
-
# errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
-
# end
-
# end
-
#
-
# Or with a block where self points to the current record to be validated:
-
#
-
# class Comment
-
# include ActiveModel::Validations
-
#
-
# validate do
-
# errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
-
# end
-
# end
-
#
-
# Note that the return value of validation methods is not relevant.
-
# It's not possible to halt the validate callback chain.
-
#
-
# Options:
-
# * <tt>:on</tt> - Specifies the contexts where this validation is active.
-
# Runs in all validation contexts by default (nil). You can pass a symbol
-
# or an array of symbols. (e.g. <tt>on: :create</tt> or
-
# <tt>on: :custom_validation_context</tt> or
-
# <tt>on: [:create, :custom_validation_context]</tt>)
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
-
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
-
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
-
# proc or string should return or evaluate to a +true+ or +false+ value.
-
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
-
# determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
-
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
-
# method, proc or string should return or evaluate to a +true+ or +false+
-
# value.
-
1
def validate(*args, &block)
-
14
options = args.extract_options!
-
-
27
if args.all? { |arg| arg.is_a?(Symbol) }
-
2
options.each_key do |k|
-
unless VALID_OPTIONS_FOR_VALIDATE.include?(k)
-
raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{VALID_OPTIONS_FOR_VALIDATE.map(&:inspect).join(', ')}. Perhaps you meant to call `validates` instead of `validate`?")
-
end
-
end
-
end
-
-
14
if options.key?(:on)
-
options = options.dup
-
options[:if] = Array(options[:if])
-
options[:if].unshift ->(o) {
-
Array(options[:on]).include?(o.validation_context)
-
}
-
end
-
-
14
args << options
-
14
set_callback(:validate, *args, &block)
-
end
-
-
# List all validators that are being used to validate the model using
-
# +validates_with+ method.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# validates_with MyValidator
-
# validates_with OtherValidator, on: :create
-
# validates_with StrictValidator, strict: true
-
# end
-
#
-
# Person.validators
-
# # => [
-
# # #<MyValidator:0x007fbff403e808 @options={}>,
-
# # #<OtherValidator:0x007fbff403d930 @options={on: :create}>,
-
# # #<StrictValidator:0x007fbff3204a30 @options={strict:true}>
-
# # ]
-
1
def validators
-
1
_validators.values.flatten.uniq
-
end
-
-
# Clears all of the validators and validations.
-
#
-
# Note that this will clear anything that is being used to validate
-
# the model for both the +validates_with+ and +validate+ methods.
-
# It clears the validators that are created with an invocation of
-
# +validates_with+ and the callbacks that are set by an invocation
-
# of +validate+.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# validates_with MyValidator
-
# validates_with OtherValidator, on: :create
-
# validates_with StrictValidator, strict: true
-
# validate :cannot_be_robot
-
#
-
# def cannot_be_robot
-
# errors.add(:base, 'A person cannot be a robot') if person_is_robot
-
# end
-
# end
-
#
-
# Person.validators
-
# # => [
-
# # #<MyValidator:0x007fbff403e808 @options={}>,
-
# # #<OtherValidator:0x007fbff403d930 @options={on: :create}>,
-
# # #<StrictValidator:0x007fbff3204a30 @options={strict:true}>
-
# # ]
-
#
-
# If one runs <tt>Person.clear_validators!</tt> and then checks to see what
-
# validators this class has, you would obtain:
-
#
-
# Person.validators # => []
-
#
-
# Also, the callback set by <tt>validate :cannot_be_robot</tt> will be erased
-
# so that:
-
#
-
# Person._validate_callbacks.empty? # => true
-
#
-
1
def clear_validators!
-
reset_callbacks(:validate)
-
_validators.clear
-
end
-
-
# List all validators that are being used to validate a specific attribute.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :name , :age
-
#
-
# validates_presence_of :name
-
# validates_inclusion_of :age, in: 0..99
-
# end
-
#
-
# Person.validators_on(:name)
-
# # => [
-
# # #<ActiveModel::Validations::PresenceValidator:0x007fe604914e60 @attributes=[:name], @options={}>,
-
# # ]
-
1
def validators_on(*attributes)
-
attributes.flat_map do |attribute|
-
_validators[attribute.to_sym]
-
end
-
end
-
-
# Returns +true+ if +attribute+ is an attribute method, +false+ otherwise.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :name
-
# end
-
#
-
# User.attribute_method?(:name) # => true
-
# User.attribute_method?(:age) # => false
-
1
def attribute_method?(attribute)
-
method_defined?(attribute)
-
end
-
-
# Copy validators on inheritance.
-
1
def inherited(base) #:nodoc:
-
4
dup = _validators.dup
-
4
base._validators = dup.each { |k, v| dup[k] = v.dup }
-
4
super
-
end
-
end
-
-
# Clean the +Errors+ object if instance is duped.
-
1
def initialize_dup(other) #:nodoc:
-
@errors = nil
-
super
-
end
-
-
# Returns the +Errors+ object that holds all information about attribute
-
# error messages.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :name
-
# validates_presence_of :name
-
# end
-
#
-
# person = Person.new
-
# person.valid? # => false
-
# person.errors # => #<ActiveModel::Errors:0x007fe603816640 @messages={name:["can't be blank"]}>
-
1
def errors
-
148
@errors ||= Errors.new(self)
-
end
-
-
# Runs all the specified validations and returns +true+ if no errors were
-
# added otherwise +false+.
-
#
-
# Aliased as validate.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :name
-
# validates_presence_of :name
-
# end
-
#
-
# person = Person.new
-
# person.name = ''
-
# person.valid? # => false
-
# person.name = 'david'
-
# person.valid? # => true
-
#
-
# Context can optionally be supplied to define which callbacks to test
-
# against (the context is defined on the validations using <tt>:on</tt>).
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :name
-
# validates_presence_of :name, on: :new
-
# end
-
#
-
# person = Person.new
-
# person.valid? # => true
-
# person.valid?(:new) # => false
-
1
def valid?(context = nil)
-
44
current_context, self.validation_context = validation_context, context
-
44
errors.clear
-
44
run_validations!
-
ensure
-
44
self.validation_context = current_context
-
end
-
-
1
alias_method :validate, :valid?
-
-
# Performs the opposite of <tt>valid?</tt>. Returns +true+ if errors were
-
# added, +false+ otherwise.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :name
-
# validates_presence_of :name
-
# end
-
#
-
# person = Person.new
-
# person.name = ''
-
# person.invalid? # => true
-
# person.name = 'david'
-
# person.invalid? # => false
-
#
-
# Context can optionally be supplied to define which callbacks to test
-
# against (the context is defined on the validations using <tt>:on</tt>).
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :name
-
# validates_presence_of :name, on: :new
-
# end
-
#
-
# person = Person.new
-
# person.invalid? # => false
-
# person.invalid?(:new) # => true
-
1
def invalid?(context = nil)
-
!valid?(context)
-
end
-
-
# Hook method defining how an attribute value should be retrieved. By default
-
# this is assumed to be an instance named after the attribute. Override this
-
# method in subclasses should you need to retrieve the value for a given
-
# attribute differently:
-
#
-
# class MyClass
-
# include ActiveModel::Validations
-
#
-
# def initialize(data = {})
-
# @data = data
-
# end
-
#
-
# def read_attribute_for_validation(key)
-
# @data[key]
-
# end
-
# end
-
1
alias :read_attribute_for_validation :send
-
-
1
protected
-
-
1
def run_validations! #:nodoc:
-
44
_run_validate_callbacks
-
44
errors.empty?
-
end
-
end
-
end
-
-
14
Dir[File.dirname(__FILE__) + "/validations/*.rb"].each { |file| require file }
-
1
module ActiveModel
-
1
module Validations
-
# == Active Model Absence Validator
-
1
class AbsenceValidator < EachValidator #:nodoc:
-
1
def validate_each(record, attr_name, value)
-
record.errors.add(attr_name, :present, options) if value.present?
-
end
-
end
-
-
1
module HelperMethods
-
# Validates that the specified attributes are blank (as defined by
-
# Object#blank?). Happens by default on save.
-
#
-
# class Person < ActiveRecord::Base
-
# validates_absence_of :first_name
-
# end
-
#
-
# The first_name attribute must be in the object and it must be blank.
-
#
-
# Configuration options:
-
# * <tt>:message</tt> - A custom error message (default is: "must be blank").
-
#
-
# There is also a list of default options supported by every validator:
-
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
-
# See <tt>ActiveModel::Validation#validates</tt> for more information
-
1
def validates_absence_of(*attr_names)
-
validates_with AbsenceValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
1
module ActiveModel
-
-
1
module Validations
-
1
class AcceptanceValidator < EachValidator # :nodoc:
-
1
def initialize(options)
-
super({ allow_nil: true, accept: "1" }.merge!(options))
-
setup!(options[:class])
-
end
-
-
1
def validate_each(record, attribute, value)
-
unless value == options[:accept]
-
record.errors.add(attribute, :accepted, options.except(:accept, :allow_nil))
-
end
-
end
-
-
1
private
-
1
def setup!(klass)
-
attr_readers = attributes.reject { |name| klass.attribute_method?(name) }
-
attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") }
-
klass.send(:attr_reader, *attr_readers)
-
klass.send(:attr_writer, *attr_writers)
-
end
-
end
-
-
1
module HelperMethods
-
# Encapsulates the pattern of wanting to validate the acceptance of a
-
# terms of service check box (or similar agreement).
-
#
-
# class Person < ActiveRecord::Base
-
# validates_acceptance_of :terms_of_service
-
# validates_acceptance_of :eula, message: 'must be abided'
-
# end
-
#
-
# If the database column does not exist, the +terms_of_service+ attribute
-
# is entirely virtual. This check is performed only if +terms_of_service+
-
# is not +nil+ and by default on save.
-
#
-
# Configuration options:
-
# * <tt>:message</tt> - A custom error message (default is: "must be
-
# accepted").
-
# * <tt>:accept</tt> - Specifies value that is considered accepted.
-
# The default value is a string "1", which makes it easy to relate to
-
# an HTML checkbox. This should be set to +true+ if you are validating
-
# a database column, since the attribute is typecast from "1" to +true+
-
# before validation.
-
#
-
# There is also a list of default options supported by every validator:
-
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
-
# See <tt>ActiveModel::Validation#validates</tt> for more information.
-
1
def validates_acceptance_of(*attr_names)
-
validates_with AcceptanceValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
1
module ActiveModel
-
1
module Validations
-
# == Active \Model \Validation \Callbacks
-
#
-
# Provides an interface for any class to have +before_validation+ and
-
# +after_validation+ callbacks.
-
#
-
# First, include ActiveModel::Validations::Callbacks from the class you are
-
# creating:
-
#
-
# class MyModel
-
# include ActiveModel::Validations::Callbacks
-
#
-
# before_validation :do_stuff_before_validation
-
# after_validation :do_stuff_after_validation
-
# end
-
#
-
# Like other <tt>before_*</tt> callbacks if +before_validation+ returns
-
# +false+ then <tt>valid?</tt> will not be called.
-
1
module Callbacks
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
include ActiveSupport::Callbacks
-
1
define_callbacks :validation,
-
terminator: ->(_,result) { result == false },
-
skip_after_callbacks_if_terminated: true,
-
scope: [:kind, :name]
-
end
-
-
1
module ClassMethods
-
# Defines a callback that will get called right before validation
-
# happens.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
# include ActiveModel::Validations::Callbacks
-
#
-
# attr_accessor :name
-
#
-
# validates_length_of :name, maximum: 6
-
#
-
# before_validation :remove_whitespaces
-
#
-
# private
-
#
-
# def remove_whitespaces
-
# name.strip!
-
# end
-
# end
-
#
-
# person = Person.new
-
# person.name = ' bob '
-
# person.valid? # => true
-
# person.name # => "bob"
-
1
def before_validation(*args, &block)
-
options = args.last
-
if options.is_a?(Hash) && options[:on]
-
options[:if] = Array(options[:if])
-
options[:on] = Array(options[:on])
-
options[:if].unshift ->(o) {
-
options[:on].include? o.validation_context
-
}
-
end
-
set_callback(:validation, :before, *args, &block)
-
end
-
-
# Defines a callback that will get called right after validation
-
# happens.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
# include ActiveModel::Validations::Callbacks
-
#
-
# attr_accessor :name, :status
-
#
-
# validates_presence_of :name
-
#
-
# after_validation :set_status
-
#
-
# private
-
#
-
# def set_status
-
# self.status = errors.empty?
-
# end
-
# end
-
#
-
# person = Person.new
-
# person.name = ''
-
# person.valid? # => false
-
# person.status # => false
-
# person.name = 'bob'
-
# person.valid? # => true
-
# person.status # => true
-
1
def after_validation(*args, &block)
-
options = args.extract_options!
-
options[:prepend] = true
-
options[:if] = Array(options[:if])
-
if options[:on]
-
options[:on] = Array(options[:on])
-
options[:if].unshift ->(o) {
-
options[:on].include? o.validation_context
-
}
-
end
-
set_callback(:validation, :after, *(args << options), &block)
-
end
-
end
-
-
1
protected
-
-
# Overwrite run validations to include callbacks.
-
1
def run_validations! #:nodoc:
-
88
_run_validation_callbacks { super }
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/range'
-
-
1
module ActiveModel
-
1
module Validations
-
1
module Clusivity #:nodoc:
-
1
ERROR_MESSAGE = "An object with the method #include? or a proc, lambda or symbol is required, " \
-
"and must be supplied as the :in (or :within) option of the configuration hash"
-
-
1
def check_validity!
-
unless delimiter.respond_to?(:include?) || delimiter.respond_to?(:call) || delimiter.respond_to?(:to_sym)
-
raise ArgumentError, ERROR_MESSAGE
-
end
-
end
-
-
1
private
-
-
1
def include?(record, value)
-
members = if delimiter.respond_to?(:call)
-
delimiter.call(record)
-
elsif delimiter.respond_to?(:to_sym)
-
record.send(delimiter)
-
else
-
delimiter
-
end
-
-
members.send(inclusion_method(members), value)
-
end
-
-
1
def delimiter
-
@delimiter ||= options[:in] || options[:within]
-
end
-
-
# In Ruby 1.9 <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all
-
# possible values in the range for equality, which is slower but more accurate.
-
# <tt>Range#cover?</tt> uses the previous logic of comparing a value with the range
-
# endpoints, which is fast but is only accurate on Numeric, Time, or DateTime ranges.
-
1
def inclusion_method(enumerable)
-
if enumerable.is_a? Range
-
case enumerable.first
-
when Numeric, Time, DateTime
-
:cover?
-
else
-
:include?
-
end
-
else
-
:include?
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveModel
-
-
1
module Validations
-
1
class ConfirmationValidator < EachValidator # :nodoc:
-
1
def initialize(options)
-
1
super
-
1
setup!(options[:class])
-
end
-
-
1
def validate_each(record, attribute, value)
-
36
if (confirmed = record.send("#{attribute}_confirmation")) && (value != confirmed)
-
2
human_attribute_name = record.class.human_attribute_name(attribute)
-
2
record.errors.add(:"#{attribute}_confirmation", :confirmation, options.merge(attribute: human_attribute_name))
-
end
-
end
-
-
1
private
-
1
def setup!(klass)
-
1
klass.send(:attr_reader, *attributes.map do |attribute|
-
1
:"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation")
-
end.compact)
-
-
1
klass.send(:attr_writer, *attributes.map do |attribute|
-
1
:"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation=")
-
end.compact)
-
end
-
end
-
-
1
module HelperMethods
-
# Encapsulates the pattern of wanting to validate a password or email
-
# address field with a confirmation.
-
#
-
# Model:
-
# class Person < ActiveRecord::Base
-
# validates_confirmation_of :user_name, :password
-
# validates_confirmation_of :email_address,
-
# message: 'should match confirmation'
-
# end
-
#
-
# View:
-
# <%= password_field "person", "password" %>
-
# <%= password_field "person", "password_confirmation" %>
-
#
-
# The added +password_confirmation+ attribute is virtual; it exists only
-
# as an in-memory attribute for validating the password. To achieve this,
-
# the validation adds accessors to the model for the confirmation
-
# attribute.
-
#
-
# NOTE: This check is performed only if +password_confirmation+ is not
-
# +nil+. To require confirmation, make sure to add a presence check for
-
# the confirmation attribute:
-
#
-
# validates_presence_of :password_confirmation, if: :password_changed?
-
#
-
# Configuration options:
-
# * <tt>:message</tt> - A custom error message (default is: "doesn't match
-
# <tt>%{translated_attribute_name}</tt>").
-
#
-
# There is also a list of default options supported by every validator:
-
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
-
# See <tt>ActiveModel::Validation#validates</tt> for more information
-
1
def validates_confirmation_of(*attr_names)
-
1
validates_with ConfirmationValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
1
require "active_model/validations/clusivity"
-
-
1
module ActiveModel
-
-
1
module Validations
-
1
class ExclusionValidator < EachValidator # :nodoc:
-
1
include Clusivity
-
-
1
def validate_each(record, attribute, value)
-
if include?(record, value)
-
record.errors.add(attribute, :exclusion, options.except(:in, :within).merge!(value: value))
-
end
-
end
-
end
-
-
1
module HelperMethods
-
# Validates that the value of the specified attribute is not in a
-
# particular enumerable object.
-
#
-
# class Person < ActiveRecord::Base
-
# validates_exclusion_of :username, in: %w( admin superuser ), message: "You don't belong here"
-
# validates_exclusion_of :age, in: 30..60, message: 'This site is only for under 30 and over 60'
-
# validates_exclusion_of :format, in: %w( mov avi ), message: "extension %{value} is not allowed"
-
# validates_exclusion_of :password, in: ->(person) { [person.username, person.first_name] },
-
# message: 'should not be the same as your username or first name'
-
# validates_exclusion_of :karma, in: :reserved_karmas
-
# end
-
#
-
# Configuration options:
-
# * <tt>:in</tt> - An enumerable object of items that the value shouldn't
-
# be part of. This can be supplied as a proc, lambda or symbol which returns an
-
# enumerable. If the enumerable is a range the test is performed with
-
# * <tt>:within</tt> - A synonym(or alias) for <tt>:in</tt>
-
# <tt>Range#cover?</tt>, otherwise with <tt>include?</tt>.
-
# * <tt>:message</tt> - Specifies a custom error message (default is: "is
-
# reserved").
-
#
-
# There is also a list of default options supported by every validator:
-
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
-
# See <tt>ActiveModel::Validation#validates</tt> for more information
-
1
def validates_exclusion_of(*attr_names)
-
validates_with ExclusionValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
1
module ActiveModel
-
-
1
module Validations
-
1
class FormatValidator < EachValidator # :nodoc:
-
1
def validate_each(record, attribute, value)
-
36
if options[:with]
-
36
regexp = option_call(record, :with)
-
36
record_error(record, attribute, :with, value) if value.to_s !~ regexp
-
elsif options[:without]
-
regexp = option_call(record, :without)
-
record_error(record, attribute, :without, value) if value.to_s =~ regexp
-
end
-
end
-
-
1
def check_validity!
-
1
unless options.include?(:with) ^ options.include?(:without) # ^ == xor, or "exclusive or"
-
raise ArgumentError, "Either :with or :without must be supplied (but not both)"
-
end
-
-
1
check_options_validity :with
-
1
check_options_validity :without
-
end
-
-
1
private
-
-
1
def option_call(record, name)
-
36
option = options[name]
-
36
option.respond_to?(:call) ? option.call(record) : option
-
end
-
-
1
def record_error(record, attribute, name, value)
-
3
record.errors.add(attribute, :invalid, options.except(name).merge!(value: value))
-
end
-
-
1
def check_options_validity(name)
-
2
if option = options[name]
-
1
if option.is_a?(Regexp)
-
1
if options[:multiline] != true && regexp_using_multiline_anchors?(option)
-
raise ArgumentError, "The provided regular expression is using multiline anchors (^ or $), " \
-
"which may present a security risk. Did you mean to use \\A and \\z, or forgot to add the " \
-
":multiline => true option?"
-
end
-
elsif !option.respond_to?(:call)
-
raise ArgumentError, "A regular expression or a proc or lambda must be supplied as :#{name}"
-
end
-
end
-
end
-
-
1
def regexp_using_multiline_anchors?(regexp)
-
1
source = regexp.source
-
1
source.start_with?("^") || (source.end_with?("$") && !source.end_with?("\\$"))
-
end
-
end
-
-
1
module HelperMethods
-
# Validates whether the value of the specified attribute is of the correct
-
# form, going by the regular expression provided. You can require that the
-
# attribute matches the regular expression:
-
#
-
# class Person < ActiveRecord::Base
-
# validates_format_of :email, with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create
-
# end
-
#
-
# Alternatively, you can require that the specified attribute does _not_
-
# match the regular expression:
-
#
-
# class Person < ActiveRecord::Base
-
# validates_format_of :email, without: /NOSPAM/
-
# end
-
#
-
# You can also provide a proc or lambda which will determine the regular
-
# expression that will be used to validate the attribute.
-
#
-
# class Person < ActiveRecord::Base
-
# # Admin can have number as a first letter in their screen name
-
# validates_format_of :screen_name,
-
# with: ->(person) { person.admin? ? /\A[a-z0-9][a-z0-9_\-]*\z/i : /\A[a-z][a-z0-9_\-]*\z/i }
-
# end
-
#
-
# Note: use <tt>\A</tt> and <tt>\Z</tt> to match the start and end of the
-
# string, <tt>^</tt> and <tt>$</tt> match the start/end of a line.
-
#
-
# Due to frequent misuse of <tt>^</tt> and <tt>$</tt>, you need to pass
-
# the <tt>multiline: true</tt> option in case you use any of these two
-
# anchors in the provided regular expression. In most cases, you should be
-
# using <tt>\A</tt> and <tt>\z</tt>.
-
#
-
# You must pass either <tt>:with</tt> or <tt>:without</tt> as an option.
-
# In addition, both must be a regular expression or a proc or lambda, or
-
# else an exception will be raised.
-
#
-
# Configuration options:
-
# * <tt>:message</tt> - A custom error message (default is: "is invalid").
-
# * <tt>:with</tt> - Regular expression that if the attribute matches will
-
# result in a successful validation. This can be provided as a proc or
-
# lambda returning regular expression which will be called at runtime.
-
# * <tt>:without</tt> - Regular expression that if the attribute does not
-
# match will result in a successful validation. This can be provided as
-
# a proc or lambda returning regular expression which will be called at
-
# runtime.
-
# * <tt>:multiline</tt> - Set to true if your regular expression contains
-
# anchors that match the beginning or end of lines as opposed to the
-
# beginning or end of the string. These anchors are <tt>^</tt> and <tt>$</tt>.
-
#
-
# There is also a list of default options supported by every validator:
-
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
-
# See <tt>ActiveModel::Validation#validates</tt> for more information
-
1
def validates_format_of(*attr_names)
-
validates_with FormatValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
1
require "active_model/validations/clusivity"
-
-
1
module ActiveModel
-
-
1
module Validations
-
1
class InclusionValidator < EachValidator # :nodoc:
-
1
include Clusivity
-
-
1
def validate_each(record, attribute, value)
-
unless include?(record, value)
-
record.errors.add(attribute, :inclusion, options.except(:in, :within).merge!(value: value))
-
end
-
end
-
end
-
-
1
module HelperMethods
-
# Validates whether the value of the specified attribute is available in a
-
# particular enumerable object.
-
#
-
# class Person < ActiveRecord::Base
-
# validates_inclusion_of :gender, in: %w( m f )
-
# validates_inclusion_of :age, in: 0..99
-
# validates_inclusion_of :format, in: %w( jpg gif png ), message: "extension %{value} is not included in the list"
-
# validates_inclusion_of :states, in: ->(person) { STATES[person.country] }
-
# validates_inclusion_of :karma, in: :available_karmas
-
# end
-
#
-
# Configuration options:
-
# * <tt>:in</tt> - An enumerable object of available items. This can be
-
# supplied as a proc, lambda or symbol which returns an enumerable. If the
-
# enumerable is a numerical range the test is performed with <tt>Range#cover?</tt>,
-
# otherwise with <tt>include?</tt>. When using a proc or lambda the instance
-
# under validation is passed as an argument.
-
# * <tt>:within</tt> - A synonym(or alias) for <tt>:in</tt>
-
# * <tt>:message</tt> - Specifies a custom error message (default is: "is
-
# not included in the list").
-
#
-
# There is also a list of default options supported by every validator:
-
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
-
# See <tt>ActiveModel::Validation#validates</tt> for more information
-
1
def validates_inclusion_of(*attr_names)
-
validates_with InclusionValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
1
module ActiveModel
-
-
# == Active \Model Length Validator
-
1
module Validations
-
1
class LengthValidator < EachValidator # :nodoc:
-
1
MESSAGES = { is: :wrong_length, minimum: :too_short, maximum: :too_long }.freeze
-
1
CHECKS = { is: :==, minimum: :>=, maximum: :<= }.freeze
-
-
1
RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :tokenizer, :too_short, :too_long]
-
-
1
def initialize(options)
-
4
if range = (options.delete(:in) || options.delete(:within))
-
raise ArgumentError, ":in and :within must be a Range" unless range.is_a?(Range)
-
options[:minimum], options[:maximum] = range.min, range.max
-
end
-
-
4
if options[:allow_blank] == false && options[:minimum].nil? && options[:is].nil?
-
options[:minimum] = 1
-
end
-
-
4
super
-
end
-
-
1
def check_validity!
-
4
keys = CHECKS.keys & options.keys
-
-
4
if keys.empty?
-
raise ArgumentError, 'Range unspecified. Specify the :in, :within, :maximum, :minimum, or :is option.'
-
end
-
-
4
keys.each do |key|
-
4
value = options[key]
-
-
4
unless (value.is_a?(Integer) && value >= 0) || value == Float::INFINITY
-
raise ArgumentError, ":#{key} must be a nonnegative Integer or Infinity"
-
end
-
end
-
end
-
-
1
def validate_each(record, attribute, value)
-
144
value = tokenize(value)
-
144
value_length = value.respond_to?(:length) ? value.length : value.to_s.length
-
144
errors_options = options.except(*RESERVED_OPTIONS)
-
-
144
CHECKS.each do |key, validity_check|
-
432
next unless check_value = options[key]
-
-
144
if !value.nil? || skip_nil_check?(key)
-
144
next if value_length.send(validity_check, check_value)
-
end
-
-
2
errors_options[:count] = check_value
-
-
2
default_message = options[MESSAGES[key]]
-
2
errors_options[:message] ||= default_message if default_message
-
-
2
record.errors.add(attribute, MESSAGES[key], errors_options)
-
end
-
end
-
-
1
private
-
-
1
def tokenize(value)
-
if options[:tokenizer] && value.kind_of?(String)
-
options[:tokenizer].call(value)
-
144
end || value
-
end
-
-
1
def skip_nil_check?(key)
-
key == :maximum && options[:allow_nil].nil? && options[:allow_blank].nil?
-
end
-
end
-
-
1
module HelperMethods
-
-
# Validates that the specified attribute matches the length restrictions
-
# supplied. Only one option can be used at a time:
-
#
-
# class Person < ActiveRecord::Base
-
# validates_length_of :first_name, maximum: 30
-
# validates_length_of :last_name, maximum: 30, message: "less than 30 if you don't mind"
-
# validates_length_of :fax, in: 7..32, allow_nil: true
-
# validates_length_of :phone, in: 7..32, allow_blank: true
-
# validates_length_of :user_name, within: 6..20, too_long: 'pick a shorter name', too_short: 'pick a longer name'
-
# validates_length_of :zip_code, minimum: 5, too_short: 'please enter at least 5 characters'
-
# validates_length_of :smurf_leader, is: 4, message: "papa is spelled with 4 characters... don't play me."
-
# validates_length_of :essay, minimum: 100, too_short: 'Your essay must be at least 100 words.',
-
# tokenizer: ->(str) { str.scan(/\w+/) }
-
# end
-
#
-
# Configuration options:
-
# * <tt>:minimum</tt> - The minimum size of the attribute.
-
# * <tt>:maximum</tt> - The maximum size of the attribute. Allows +nil+ by
-
# default if not used with :minimum.
-
# * <tt>:is</tt> - The exact size of the attribute.
-
# * <tt>:within</tt> - A range specifying the minimum and maximum size of
-
# the attribute.
-
# * <tt>:in</tt> - A synonym (or alias) for <tt>:within</tt>.
-
# * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
-
# * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
-
# * <tt>:too_long</tt> - The error message if the attribute goes over the
-
# maximum (default is: "is too long (maximum is %{count} characters)").
-
# * <tt>:too_short</tt> - The error message if the attribute goes under the
-
# minimum (default is: "is too short (min is %{count} characters)").
-
# * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt>
-
# method and the attribute is the wrong size (default is: "is the wrong
-
# length (should be %{count} characters)").
-
# * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>,
-
# <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate
-
# <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message.
-
# * <tt>:tokenizer</tt> - Specifies how to split up the attribute string.
-
# (e.g. <tt>tokenizer: ->(str) { str.scan(/\w+/) }</tt> to count words
-
# as in above example). Defaults to <tt>->(value) { value.split(//) }</tt>
-
# which counts individual characters.
-
#
-
# There is also a list of default options supported by every validator:
-
# +:if+, +:unless+, +:on+ and +:strict+.
-
# See <tt>ActiveModel::Validation#validates</tt> for more information
-
1
def validates_length_of(*attr_names)
-
1
validates_with LengthValidator, _merge_attributes(attr_names)
-
end
-
-
1
alias_method :validates_size_of, :validates_length_of
-
end
-
end
-
end
-
1
module ActiveModel
-
-
1
module Validations
-
1
class NumericalityValidator < EachValidator # :nodoc:
-
1
CHECKS = { greater_than: :>, greater_than_or_equal_to: :>=,
-
equal_to: :==, less_than: :<, less_than_or_equal_to: :<=,
-
odd: :odd?, even: :even?, other_than: :!= }.freeze
-
-
1
RESERVED_OPTIONS = CHECKS.keys + [:only_integer]
-
-
1
def check_validity!
-
keys = CHECKS.keys - [:odd, :even]
-
options.slice(*keys).each do |option, value|
-
unless value.is_a?(Numeric) || value.is_a?(Proc) || value.is_a?(Symbol)
-
raise ArgumentError, ":#{option} must be a number, a symbol or a proc"
-
end
-
end
-
end
-
-
1
def validate_each(record, attr_name, value)
-
before_type_cast = :"#{attr_name}_before_type_cast"
-
-
raw_value = record.send(before_type_cast) if record.respond_to?(before_type_cast)
-
raw_value ||= value
-
-
if record_attribute_changed_in_place?(record, attr_name)
-
raw_value = value
-
end
-
-
return if options[:allow_nil] && raw_value.nil?
-
-
unless value = parse_raw_value_as_a_number(raw_value)
-
record.errors.add(attr_name, :not_a_number, filtered_options(raw_value))
-
return
-
end
-
-
if allow_only_integer?(record)
-
unless value = parse_raw_value_as_an_integer(raw_value)
-
record.errors.add(attr_name, :not_an_integer, filtered_options(raw_value))
-
return
-
end
-
end
-
-
options.slice(*CHECKS.keys).each do |option, option_value|
-
case option
-
when :odd, :even
-
unless value.to_i.send(CHECKS[option])
-
record.errors.add(attr_name, option, filtered_options(value))
-
end
-
else
-
case option_value
-
when Proc
-
option_value = option_value.call(record)
-
when Symbol
-
option_value = record.send(option_value)
-
end
-
-
unless value.send(CHECKS[option], option_value)
-
record.errors.add(attr_name, option, filtered_options(value).merge!(count: option_value))
-
end
-
end
-
end
-
end
-
-
1
protected
-
-
1
def parse_raw_value_as_a_number(raw_value)
-
Kernel.Float(raw_value) if raw_value !~ /\A0[xX]/
-
rescue ArgumentError, TypeError
-
nil
-
end
-
-
1
def parse_raw_value_as_an_integer(raw_value)
-
raw_value.to_i if raw_value.to_s =~ /\A[+-]?\d+\z/
-
end
-
-
1
def filtered_options(value)
-
filtered = options.except(*RESERVED_OPTIONS)
-
filtered[:value] = value
-
filtered
-
end
-
-
1
def allow_only_integer?(record)
-
case options[:only_integer]
-
when Symbol
-
record.send(options[:only_integer])
-
when Proc
-
options[:only_integer].call(record)
-
else
-
options[:only_integer]
-
end
-
end
-
-
1
private
-
-
1
def record_attribute_changed_in_place?(record, attr_name)
-
record.respond_to?(:attribute_changed_in_place?) &&
-
record.attribute_changed_in_place?(attr_name.to_s)
-
end
-
end
-
-
1
module HelperMethods
-
# Validates whether the value of the specified attribute is numeric by
-
# trying to convert it to a float with Kernel.Float (if <tt>only_integer</tt>
-
# is +false+) or applying it to the regular expression <tt>/\A[\+\-]?\d+\Z/</tt>
-
# (if <tt>only_integer</tt> is set to +true+).
-
#
-
# class Person < ActiveRecord::Base
-
# validates_numericality_of :value, on: :create
-
# end
-
#
-
# Configuration options:
-
# * <tt>:message</tt> - A custom error message (default is: "is not a number").
-
# * <tt>:only_integer</tt> - Specifies whether the value has to be an
-
# integer, e.g. an integral value (default is +false+).
-
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is
-
# +false+). Notice that for fixnum and float columns empty strings are
-
# converted to +nil+.
-
# * <tt>:greater_than</tt> - Specifies the value must be greater than the
-
# supplied value.
-
# * <tt>:greater_than_or_equal_to</tt> - Specifies the value must be
-
# greater than or equal the supplied value.
-
# * <tt>:equal_to</tt> - Specifies the value must be equal to the supplied
-
# value.
-
# * <tt>:less_than</tt> - Specifies the value must be less than the
-
# supplied value.
-
# * <tt>:less_than_or_equal_to</tt> - Specifies the value must be less
-
# than or equal the supplied value.
-
# * <tt>:other_than</tt> - Specifies the value must be other than the
-
# supplied value.
-
# * <tt>:odd</tt> - Specifies the value must be an odd number.
-
# * <tt>:even</tt> - Specifies the value must be an even number.
-
#
-
# There is also a list of default options supported by every validator:
-
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ .
-
# See <tt>ActiveModel::Validation#validates</tt> for more information
-
#
-
# The following checks can also be supplied with a proc or a symbol which
-
# corresponds to a method:
-
#
-
# * <tt>:greater_than</tt>
-
# * <tt>:greater_than_or_equal_to</tt>
-
# * <tt>:equal_to</tt>
-
# * <tt>:less_than</tt>
-
# * <tt>:less_than_or_equal_to</tt>
-
# * <tt>:only_integer</tt>
-
#
-
# For example:
-
#
-
# class Person < ActiveRecord::Base
-
# validates_numericality_of :width, less_than: ->(person) { person.height }
-
# validates_numericality_of :width, greater_than: :minimum_weight
-
# end
-
1
def validates_numericality_of(*attr_names)
-
validates_with NumericalityValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
-
1
module ActiveModel
-
-
1
module Validations
-
1
class PresenceValidator < EachValidator # :nodoc:
-
1
def validate_each(record, attr_name, value)
-
180
record.errors.add(attr_name, :blank, options) if value.blank?
-
end
-
end
-
-
1
module HelperMethods
-
# Validates that the specified attributes are not blank (as defined by
-
# Object#blank?). Happens by default on save.
-
#
-
# class Person < ActiveRecord::Base
-
# validates_presence_of :first_name
-
# end
-
#
-
# The first_name attribute must be in the object and it cannot be blank.
-
#
-
# If you want to validate the presence of a boolean field (where the real
-
# values are +true+ and +false+), you will want to use
-
# <tt>validates_inclusion_of :field_name, in: [true, false]</tt>.
-
#
-
# This is due to the way Object#blank? handles boolean values:
-
# <tt>false.blank? # => true</tt>.
-
#
-
# Configuration options:
-
# * <tt>:message</tt> - A custom error message (default is: "can't be blank").
-
#
-
# There is also a list of default options supported by every validator:
-
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
-
# See <tt>ActiveModel::Validation#validates</tt> for more information
-
1
def validates_presence_of(*attr_names)
-
validates_with PresenceValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/hash/slice'
-
-
1
module ActiveModel
-
1
module Validations
-
1
module ClassMethods
-
# This method is a shortcut to all default validators and any custom
-
# validator classes ending in 'Validator'. Note that Rails default
-
# validators can be overridden inside specific classes by creating
-
# custom validator classes in their place such as PresenceValidator.
-
#
-
# Examples of using the default rails validators:
-
#
-
# validates :terms, acceptance: true
-
# validates :password, confirmation: true
-
# validates :username, exclusion: { in: %w(admin superuser) }
-
# validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create }
-
# validates :age, inclusion: { in: 0..9 }
-
# validates :first_name, length: { maximum: 30 }
-
# validates :age, numericality: true
-
# validates :username, presence: true
-
# validates :username, uniqueness: true
-
#
-
# The power of the +validates+ method comes when using custom validators
-
# and default validators in one call for a given attribute.
-
#
-
# class EmailValidator < ActiveModel::EachValidator
-
# def validate_each(record, attribute, value)
-
# record.errors.add attribute, (options[:message] || "is not an email") unless
-
# value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
-
# end
-
# end
-
#
-
# class Person
-
# include ActiveModel::Validations
-
# attr_accessor :name, :email
-
#
-
# validates :name, presence: true, uniqueness: true, length: { maximum: 100 }
-
# validates :email, presence: true, email: true
-
# end
-
#
-
# Validator classes may also exist within the class being validated
-
# allowing custom modules of validators to be included as needed.
-
#
-
# class Film
-
# include ActiveModel::Validations
-
#
-
# class TitleValidator < ActiveModel::EachValidator
-
# def validate_each(record, attribute, value)
-
# record.errors.add attribute, "must start with 'the'" unless value =~ /\Athe/i
-
# end
-
# end
-
#
-
# validates :name, title: true
-
# end
-
#
-
# Additionally validator classes may be in another namespace and still
-
# used within any class.
-
#
-
# validates :name, :'film/title' => true
-
#
-
# The validators hash can also handle regular expressions, ranges, arrays
-
# and strings in shortcut form.
-
#
-
# validates :email, format: /@/
-
# validates :gender, inclusion: %w(male female)
-
# validates :password, length: 6..20
-
#
-
# When using shortcut form, ranges and arrays are passed to your
-
# validator's initializer as <tt>options[:in]</tt> while other types
-
# including regular expressions and strings are passed as <tt>options[:with]</tt>.
-
#
-
# There is also a list of options that could be used along with validators:
-
#
-
# * <tt>:on</tt> - Specifies the contexts where this validation is active.
-
# Runs in all validation contexts by default (nil). You can pass a symbol
-
# or an array of symbols. (e.g. <tt>on: :create</tt> or
-
# <tt>on: :custom_validation_context</tt> or
-
# <tt>on: [:create, :custom_validation_context]</tt>)
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
-
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
-
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
-
# proc or string should return or evaluate to a +true+ or +false+ value.
-
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
-
# if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
-
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
-
# method, proc or string should return or evaluate to a +true+ or
-
# +false+ value.
-
# * <tt>:allow_nil</tt> - Skip validation if the attribute is +nil+.
-
# * <tt>:allow_blank</tt> - Skip validation if the attribute is blank.
-
# * <tt>:strict</tt> - If the <tt>:strict</tt> option is set to true
-
# will raise ActiveModel::StrictValidationFailed instead of adding the error.
-
# <tt>:strict</tt> option can also be set to any other exception.
-
#
-
# Example:
-
#
-
# validates :password, presence: true, confirmation: true, if: :password_required?
-
# validates :token, uniqueness: true, strict: TokenGenerationException
-
#
-
#
-
# Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+, +:allow_nil+, +:strict+
-
# and +:message+ can be given to one specific validator, as a hash:
-
#
-
# validates :password, presence: { if: :password_required?, message: 'is forgotten.' }, confirmation: true
-
1
def validates(*attributes)
-
5
defaults = attributes.extract_options!.dup
-
5
validations = defaults.slice!(*_validates_default_keys)
-
-
5
raise ArgumentError, "You need to supply at least one attribute" if attributes.empty?
-
5
raise ArgumentError, "You need to supply at least one validation" if validations.empty?
-
-
5
defaults[:attributes] = attributes
-
-
5
validations.each do |key, options|
-
10
next unless options
-
10
key = "#{key.to_s.camelize}Validator"
-
-
10
begin
-
10
validator = key.include?('::') ? key.constantize : const_get(key)
-
rescue NameError
-
raise ArgumentError, "Unknown validator: '#{key}'"
-
end
-
-
10
validates_with(validator, defaults.merge(_parse_validates_options(options)))
-
end
-
end
-
-
# This method is used to define validations that cannot be corrected by end
-
# users and are considered exceptional. So each validator defined with bang
-
# or <tt>:strict</tt> option set to <tt>true</tt> will always raise
-
# <tt>ActiveModel::StrictValidationFailed</tt> instead of adding error
-
# when validation fails. See <tt>validates</tt> for more information about
-
# the validation itself.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :name
-
# validates! :name, presence: true
-
# end
-
#
-
# person = Person.new
-
# person.name = ''
-
# person.valid?
-
# # => ActiveModel::StrictValidationFailed: Name can't be blank
-
1
def validates!(*attributes)
-
options = attributes.extract_options!
-
options[:strict] = true
-
validates(*(attributes << options))
-
end
-
-
1
protected
-
-
# When creating custom validators, it might be useful to be able to specify
-
# additional default keys. This can be done by overwriting this method.
-
1
def _validates_default_keys # :nodoc:
-
5
[:if, :unless, :on, :allow_blank, :allow_nil , :strict]
-
end
-
-
1
def _parse_validates_options(options) # :nodoc:
-
10
case options
-
when TrueClass
-
5
{}
-
when Hash
-
5
options
-
when Range, Array
-
{ in: options }
-
else
-
{ with: options }
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveModel
-
1
module Validations
-
1
module HelperMethods
-
1
private
-
1
def _merge_attributes(attr_names)
-
2
options = attr_names.extract_options!.symbolize_keys
-
2
attr_names.flatten!
-
2
options[:attributes] = attr_names
-
2
options
-
end
-
end
-
-
1
class WithValidator < EachValidator # :nodoc:
-
1
def validate_each(record, attr, val)
-
method_name = options[:with]
-
-
if record.method(method_name).arity == 0
-
record.send method_name
-
else
-
record.send method_name, attr
-
end
-
end
-
end
-
-
1
module ClassMethods
-
# Passes the record off to the class or classes specified and allows them
-
# to add errors based on more complex conditions.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
# validates_with MyValidator
-
# end
-
#
-
# class MyValidator < ActiveModel::Validator
-
# def validate(record)
-
# if some_complex_logic
-
# record.errors.add :base, 'This record is invalid'
-
# end
-
# end
-
#
-
# private
-
# def some_complex_logic
-
# # ...
-
# end
-
# end
-
#
-
# You may also pass it multiple classes, like so:
-
#
-
# class Person
-
# include ActiveModel::Validations
-
# validates_with MyValidator, MyOtherValidator, on: :create
-
# end
-
#
-
# Configuration options:
-
# * <tt>:on</tt> - Specifies the contexts where this validation is active.
-
# Runs in all validation contexts by default (nil). You can pass a symbol
-
# or an array of symbols. (e.g. <tt>on: :create</tt> or
-
# <tt>on: :custom_validation_context</tt> or
-
# <tt>on: [:create, :custom_validation_context]</tt>)
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
-
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
-
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>).
-
# The method, proc or string should return or evaluate to a +true+ or
-
# +false+ value.
-
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
-
# determine if the validation should not occur
-
# (e.g. <tt>unless: :skip_validation</tt>, or
-
# <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>).
-
# The method, proc or string should return or evaluate to a +true+ or
-
# +false+ value.
-
# * <tt>:strict</tt> - Specifies whether validation should be strict.
-
# See <tt>ActiveModel::Validation#validates!</tt> for more information.
-
#
-
# If you pass any additional configuration options, they will be passed
-
# to the class and available as +options+:
-
#
-
# class Person
-
# include ActiveModel::Validations
-
# validates_with MyValidator, my_custom_key: 'my custom value'
-
# end
-
#
-
# class MyValidator < ActiveModel::Validator
-
# def validate(record)
-
# options[:my_custom_key] # => "my custom value"
-
# end
-
# end
-
1
def validates_with(*args, &block)
-
12
options = args.extract_options!
-
12
options[:class] = self
-
-
12
args.each do |klass|
-
12
validator = klass.new(options, &block)
-
-
12
if validator.respond_to?(:attributes) && !validator.attributes.empty?
-
12
validator.attributes.each do |attribute|
-
12
_validators[attribute.to_sym] << validator
-
end
-
else
-
_validators[nil] << validator
-
end
-
-
12
validate(validator, options)
-
end
-
end
-
end
-
-
# Passes the record off to the class or classes specified and allows them
-
# to add errors based on more complex conditions.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# validate :instance_validations
-
#
-
# def instance_validations
-
# validates_with MyValidator
-
# end
-
# end
-
#
-
# Please consult the class method documentation for more information on
-
# creating your own validator.
-
#
-
# You may also pass it multiple classes, like so:
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# validate :instance_validations, on: :create
-
#
-
# def instance_validations
-
# validates_with MyValidator, MyOtherValidator
-
# end
-
# end
-
#
-
# Standard configuration options (<tt>:on</tt>, <tt>:if</tt> and
-
# <tt>:unless</tt>), which are available on the class version of
-
# +validates_with+, should instead be placed on the +validates+ method
-
# as these are applied and tested in the callback.
-
#
-
# If you pass any additional configuration options, they will be passed
-
# to the class and available as +options+, please refer to the
-
# class version of this method for more information.
-
1
def validates_with(*args, &block)
-
options = args.extract_options!
-
options[:class] = self.class
-
-
args.each do |klass|
-
validator = klass.new(options, &block)
-
validator.validate(self)
-
end
-
end
-
end
-
end
-
1
require "active_support/core_ext/module/anonymous"
-
-
1
module ActiveModel
-
-
# == Active \Model \Validator
-
#
-
# A simple base class that can be used along with
-
# ActiveModel::Validations::ClassMethods.validates_with
-
#
-
# class Person
-
# include ActiveModel::Validations
-
# validates_with MyValidator
-
# end
-
#
-
# class MyValidator < ActiveModel::Validator
-
# def validate(record)
-
# if some_complex_logic
-
# record.errors[:base] = "This record is invalid"
-
# end
-
# end
-
#
-
# private
-
# def some_complex_logic
-
# # ...
-
# end
-
# end
-
#
-
# Any class that inherits from ActiveModel::Validator must implement a method
-
# called +validate+ which accepts a +record+.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
# validates_with MyValidator
-
# end
-
#
-
# class MyValidator < ActiveModel::Validator
-
# def validate(record)
-
# record # => The person instance being validated
-
# options # => Any non-standard options passed to validates_with
-
# end
-
# end
-
#
-
# To cause a validation error, you must add to the +record+'s errors directly
-
# from within the validators message.
-
#
-
# class MyValidator < ActiveModel::Validator
-
# def validate(record)
-
# record.errors.add :base, "This is some custom error message"
-
# record.errors.add :first_name, "This is some complex validation"
-
# # etc...
-
# end
-
# end
-
#
-
# To add behavior to the initialize method, use the following signature:
-
#
-
# class MyValidator < ActiveModel::Validator
-
# def initialize(options)
-
# super
-
# @my_custom_field = options[:field_name] || :first_name
-
# end
-
# end
-
#
-
# Note that the validator is initialized only once for the whole application
-
# life cycle, and not on each validation run.
-
#
-
# The easiest way to add custom validators for validating individual attributes
-
# is with the convenient <tt>ActiveModel::EachValidator</tt>.
-
#
-
# class TitleValidator < ActiveModel::EachValidator
-
# def validate_each(record, attribute, value)
-
# record.errors.add attribute, 'must be Mr., Mrs., or Dr.' unless %w(Mr. Mrs. Dr.).include?(value)
-
# end
-
# end
-
#
-
# This can now be used in combination with the +validates+ method
-
# (see <tt>ActiveModel::Validations::ClassMethods.validates</tt> for more on this).
-
#
-
# class Person
-
# include ActiveModel::Validations
-
# attr_accessor :title
-
#
-
# validates :title, presence: true, title: true
-
# end
-
#
-
# It can be useful to access the class that is using that validator when there are prerequisites such
-
# as an +attr_accessor+ being present. This class is accessible via +options[:class]+ in the constructor.
-
# To setup your validator override the constructor.
-
#
-
# class MyValidator < ActiveModel::Validator
-
# def initialize(options={})
-
# super
-
# options[:class].send :attr_accessor, :custom_attribute
-
# end
-
# end
-
1
class Validator
-
1
attr_reader :options
-
-
# Returns the kind of the validator.
-
#
-
# PresenceValidator.kind # => :presence
-
# UniquenessValidator.kind # => :uniqueness
-
1
def self.kind
-
@kind ||= name.split('::').last.underscore.sub(/_validator$/, '').to_sym unless anonymous?
-
end
-
-
# Accepts options that will be made available through the +options+ reader.
-
1
def initialize(options = {})
-
12
@options = options.except(:class).freeze
-
end
-
-
# Returns the kind for this validator.
-
#
-
# PresenceValidator.new.kind # => :presence
-
# UniquenessValidator.new.kind # => :uniqueness
-
1
def kind
-
self.class.kind
-
end
-
-
# Override this method in subclasses with validation logic, adding errors
-
# to the records +errors+ array where necessary.
-
1
def validate(record)
-
raise NotImplementedError, "Subclasses must implement a validate(record) method."
-
end
-
end
-
-
# +EachValidator+ is a validator which iterates through the attributes given
-
# in the options hash invoking the <tt>validate_each</tt> method passing in the
-
# record, attribute and value.
-
#
-
# All Active Model validations are built on top of this validator.
-
1
class EachValidator < Validator #:nodoc:
-
1
attr_reader :attributes
-
-
# Returns a new validator instance. All options will be available via the
-
# +options+ reader, however the <tt>:attributes</tt> option will be removed
-
# and instead be made available through the +attributes+ reader.
-
1
def initialize(options)
-
12
@attributes = Array(options.delete(:attributes))
-
12
raise ArgumentError, ":attributes cannot be blank" if @attributes.empty?
-
12
super
-
12
check_validity!
-
end
-
-
# Performs validation on the supplied record. By default this will call
-
# +validates_each+ to determine validity therefore subclasses should
-
# override +validates_each+ with validation logic.
-
1
def validate(record)
-
432
attributes.each do |attribute|
-
432
value = record.read_attribute_for_validation(attribute)
-
432
next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
-
432
validate_each(record, attribute, value)
-
end
-
end
-
-
# Override this method in subclasses with the validation logic, adding
-
# errors to the records +errors+ array where necessary.
-
1
def validate_each(record, attribute, value)
-
raise NotImplementedError, "Subclasses must implement a validate_each(record, attribute, value) method"
-
end
-
-
# Hook method that gets called by the initializer allowing verification
-
# that the arguments supplied are valid. You could for example raise an
-
# +ArgumentError+ when invalid options are supplied.
-
1
def check_validity!
-
end
-
end
-
-
# +BlockValidator+ is a special +EachValidator+ which receives a block on initialization
-
# and call this block for each attribute being validated. +validates_each+ uses this validator.
-
1
class BlockValidator < EachValidator #:nodoc:
-
1
def initialize(options, &block)
-
@block = block
-
super
-
end
-
-
1
private
-
-
1
def validate_each(record, attribute, value)
-
@block.call(record, attribute, value)
-
end
-
end
-
end
-
1
module ActiveRecord
-
# = Active Record Aggregations
-
1
module Aggregations # :nodoc:
-
1
extend ActiveSupport::Concern
-
-
1
def clear_aggregation_cache #:nodoc:
-
@aggregation_cache.clear if persisted?
-
end
-
-
# Active Record implements aggregation through a macro-like class method called +composed_of+
-
# for representing attributes as value objects. It expresses relationships like "Account [is]
-
# composed of Money [among other things]" or "Person [is] composed of [an] address". Each call
-
# to the macro adds a description of how the value objects are created from the attributes of
-
# the entity object (when the entity is initialized either as a new object or from finding an
-
# existing object) and how it can be turned back into attributes (when the entity is saved to
-
# the database).
-
#
-
# class Customer < ActiveRecord::Base
-
# composed_of :balance, class_name: "Money", mapping: %w(balance amount)
-
# composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
-
# end
-
#
-
# The customer class now has the following methods to manipulate the value objects:
-
# * <tt>Customer#balance, Customer#balance=(money)</tt>
-
# * <tt>Customer#address, Customer#address=(address)</tt>
-
#
-
# These methods will operate with value objects like the ones described below:
-
#
-
# class Money
-
# include Comparable
-
# attr_reader :amount, :currency
-
# EXCHANGE_RATES = { "USD_TO_DKK" => 6 }
-
#
-
# def initialize(amount, currency = "USD")
-
# @amount, @currency = amount, currency
-
# end
-
#
-
# def exchange_to(other_currency)
-
# exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor
-
# Money.new(exchanged_amount, other_currency)
-
# end
-
#
-
# def ==(other_money)
-
# amount == other_money.amount && currency == other_money.currency
-
# end
-
#
-
# def <=>(other_money)
-
# if currency == other_money.currency
-
# amount <=> other_money.amount
-
# else
-
# amount <=> other_money.exchange_to(currency).amount
-
# end
-
# end
-
# end
-
#
-
# class Address
-
# attr_reader :street, :city
-
# def initialize(street, city)
-
# @street, @city = street, city
-
# end
-
#
-
# def close_to?(other_address)
-
# city == other_address.city
-
# end
-
#
-
# def ==(other_address)
-
# city == other_address.city && street == other_address.street
-
# end
-
# end
-
#
-
# Now it's possible to access attributes from the database through the value objects instead. If
-
# you choose to name the composition the same as the attribute's name, it will be the only way to
-
# access that attribute. That's the case with our +balance+ attribute. You interact with the value
-
# objects just like you would with any other attribute:
-
#
-
# customer.balance = Money.new(20) # sets the Money value object and the attribute
-
# customer.balance # => Money value object
-
# customer.balance.exchange_to("DKK") # => Money.new(120, "DKK")
-
# customer.balance > Money.new(10) # => true
-
# customer.balance == Money.new(20) # => true
-
# customer.balance < Money.new(5) # => false
-
#
-
# Value objects can also be composed of multiple attributes, such as the case of Address. The order
-
# of the mappings will determine the order of the parameters.
-
#
-
# customer.address_street = "Hyancintvej"
-
# customer.address_city = "Copenhagen"
-
# customer.address # => Address.new("Hyancintvej", "Copenhagen")
-
#
-
# customer.address_street = "Vesterbrogade"
-
# customer.address # => Address.new("Hyancintvej", "Copenhagen")
-
# customer.clear_aggregation_cache
-
# customer.address # => Address.new("Vesterbrogade", "Copenhagen")
-
#
-
# customer.address = Address.new("May Street", "Chicago")
-
# customer.address_street # => "May Street"
-
# customer.address_city # => "Chicago"
-
#
-
# == Writing value objects
-
#
-
# Value objects are immutable and interchangeable objects that represent a given value, such as
-
# a Money object representing $5. Two Money objects both representing $5 should be equal (through
-
# methods such as <tt>==</tt> and <tt><=></tt> from Comparable if ranking makes sense). This is
-
# unlike entity objects where equality is determined by identity. An entity class such as Customer can
-
# easily have two different objects that both have an address on Hyancintvej. Entity identity is
-
# determined by object or relational unique identifiers (such as primary keys). Normal
-
# ActiveRecord::Base classes are entity objects.
-
#
-
# It's also important to treat the value objects as immutable. Don't allow the Money object to have
-
# its amount changed after creation. Create a new Money object with the new value instead. The
-
# Money#exchange_to method is an example of this. It returns a new value object instead of changing
-
# its own values. Active Record won't persist value objects that have been changed through means
-
# other than the writer method.
-
#
-
# The immutable requirement is enforced by Active Record by freezing any object assigned as a value
-
# object. Attempting to change it afterwards will result in a RuntimeError.
-
#
-
# Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not
-
# keeping value objects immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
-
#
-
# == Custom constructors and converters
-
#
-
# By default value objects are initialized by calling the <tt>new</tt> constructor of the value
-
# class passing each of the mapped attributes, in the order specified by the <tt>:mapping</tt>
-
# option, as arguments. If the value class doesn't support this convention then +composed_of+ allows
-
# a custom constructor to be specified.
-
#
-
# When a new value is assigned to the value object, the default assumption is that the new value
-
# is an instance of the value class. Specifying a custom converter allows the new value to be automatically
-
# converted to an instance of value class if necessary.
-
#
-
# For example, the NetworkResource model has +network_address+ and +cidr_range+ attributes that should be
-
# aggregated using the NetAddr::CIDR value class (http://www.ruby-doc.org/gems/docs/n/netaddr-1.5.0/NetAddr/CIDR.html).
-
# The constructor for the value class is called +create+ and it expects a CIDR address string as a parameter.
-
# New values can be assigned to the value object using either another NetAddr::CIDR object, a string
-
# or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to meet
-
# these requirements:
-
#
-
# class NetworkResource < ActiveRecord::Base
-
# composed_of :cidr,
-
# class_name: 'NetAddr::CIDR',
-
# mapping: [ %w(network_address network), %w(cidr_range bits) ],
-
# allow_nil: true,
-
# constructor: Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") },
-
# converter: Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) }
-
# end
-
#
-
# # This calls the :constructor
-
# network_resource = NetworkResource.new(network_address: '192.168.0.1', cidr_range: 24)
-
#
-
# # These assignments will both use the :converter
-
# network_resource.cidr = [ '192.168.2.1', 8 ]
-
# network_resource.cidr = '192.168.0.1/24'
-
#
-
# # This assignment won't use the :converter as the value is already an instance of the value class
-
# network_resource.cidr = NetAddr::CIDR.create('192.168.2.1/8')
-
#
-
# # Saving and then reloading will use the :constructor on reload
-
# network_resource.save
-
# network_resource.reload
-
#
-
# == Finding records by a value object
-
#
-
# Once a +composed_of+ relationship is specified for a model, records can be loaded from the database
-
# by specifying an instance of the value object in the conditions hash. The following example
-
# finds all customers with +balance_amount+ equal to 20 and +balance_currency+ equal to "USD":
-
#
-
# Customer.where(balance: Money.new(20, "USD"))
-
#
-
1
module ClassMethods
-
# Adds reader and writer methods for manipulating a value object:
-
# <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods.
-
#
-
# Options are:
-
# * <tt>:class_name</tt> - Specifies the class name of the association. Use it only if that name
-
# can't be inferred from the part id. So <tt>composed_of :address</tt> will by default be linked
-
# to the Address class, but if the real class name is CompanyAddress, you'll have to specify it
-
# with this option.
-
# * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value
-
# object. Each mapping is represented as an array where the first item is the name of the
-
# entity attribute and the second item is the name of the attribute in the value object. The
-
# order in which mappings are defined determines the order in which attributes are sent to the
-
# value class constructor.
-
# * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped
-
# attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all
-
# mapped attributes.
-
# This defaults to +false+.
-
# * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that
-
# is called to initialize the value object. The constructor is passed all of the mapped attributes,
-
# in the order that they are defined in the <tt>:mapping option</tt>, as arguments and uses them
-
# to instantiate a <tt>:class_name</tt> object.
-
# The default is <tt>:new</tt>.
-
# * <tt>:converter</tt> - A symbol specifying the name of a class method of <tt>:class_name</tt>
-
# or a Proc that is called when a new value is assigned to the value object. The converter is
-
# passed the single value that is used in the assignment and is only called if the new value is
-
# not an instance of <tt>:class_name</tt>. If <tt>:allow_nil</tt> is set to true, the converter
-
# can return nil to skip the assignment.
-
#
-
# Option examples:
-
# composed_of :temperature, mapping: %w(reading celsius)
-
# composed_of :balance, class_name: "Money", mapping: %w(balance amount),
-
# converter: Proc.new { |balance| balance.to_money }
-
# composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
-
# composed_of :gps_location
-
# composed_of :gps_location, allow_nil: true
-
# composed_of :ip_address,
-
# class_name: 'IPAddr',
-
# mapping: %w(ip to_i),
-
# constructor: Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) },
-
# converter: Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) }
-
#
-
1
def composed_of(part_id, options = {})
-
options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter)
-
-
name = part_id.id2name
-
class_name = options[:class_name] || name.camelize
-
mapping = options[:mapping] || [ name, name ]
-
mapping = [ mapping ] unless mapping.first.is_a?(Array)
-
allow_nil = options[:allow_nil] || false
-
constructor = options[:constructor] || :new
-
converter = options[:converter]
-
-
reader_method(name, class_name, mapping, allow_nil, constructor)
-
writer_method(name, class_name, mapping, allow_nil, converter)
-
-
reflection = ActiveRecord::Reflection.create(:composed_of, part_id, nil, options, self)
-
Reflection.add_aggregate_reflection self, part_id, reflection
-
end
-
-
1
private
-
1
def reader_method(name, class_name, mapping, allow_nil, constructor)
-
define_method(name) do
-
if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|key, _| !_read_attribute(key).nil? })
-
attrs = mapping.collect {|key, _| _read_attribute(key)}
-
object = constructor.respond_to?(:call) ?
-
constructor.call(*attrs) :
-
class_name.constantize.send(constructor, *attrs)
-
@aggregation_cache[name] = object
-
end
-
@aggregation_cache[name]
-
end
-
end
-
-
1
def writer_method(name, class_name, mapping, allow_nil, converter)
-
define_method("#{name}=") do |part|
-
klass = class_name.constantize
-
if part.is_a?(Hash)
-
part = klass.new(*part.values)
-
end
-
-
unless part.is_a?(klass) || converter.nil? || part.nil?
-
part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part)
-
end
-
-
if part.nil? && allow_nil
-
mapping.each { |key, _| self[key] = nil }
-
@aggregation_cache[name] = nil
-
else
-
mapping.each { |key, value| self[key] = part.send(value) }
-
@aggregation_cache[name] = part.freeze
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
class AssociationRelation < Relation
-
1
def initialize(klass, table, association)
-
super(klass, table)
-
@association = association
-
end
-
-
1
def proxy_association
-
@association
-
end
-
-
1
def ==(other)
-
other == to_a
-
end
-
-
1
def build(*args, &block)
-
scoping { @association.build(*args, &block) }
-
end
-
1
alias new build
-
-
1
def create(*args, &block)
-
scoping { @association.create(*args, &block) }
-
end
-
-
1
def create!(*args, &block)
-
scoping { @association.create!(*args, &block) }
-
end
-
-
1
private
-
-
1
def exec_queries
-
super.each { |r| @association.set_inverse_instance r }
-
end
-
end
-
end
-
1
require 'active_support/core_ext/enumerable'
-
1
require 'active_support/core_ext/string/conversions'
-
1
require 'active_support/core_ext/module/remove_method'
-
1
require 'active_record/errors'
-
-
1
module ActiveRecord
-
1
class AssociationNotFoundError < ConfigurationError #:nodoc:
-
1
def initialize(record, association_name)
-
super("Association named '#{association_name}' was not found on #{record.class.name}; perhaps you misspelled it?")
-
end
-
end
-
-
1
class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
-
1
def initialize(reflection, associated_class = nil)
-
super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{associated_class.nil? ? reflection.class_name : associated_class.name})")
-
end
-
end
-
-
1
class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
-
1
def initialize(owner_class_name, reflection)
-
super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class_name}")
-
end
-
end
-
-
1
class HasManyThroughAssociationPolymorphicSourceError < ActiveRecordError #:nodoc:
-
1
def initialize(owner_class_name, reflection, source_reflection)
-
super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}' without 'source_type'. Try adding 'source_type: \"#{reflection.name.to_s.classify}\"' to 'has_many :through' definition.")
-
end
-
end
-
-
1
class HasManyThroughAssociationPolymorphicThroughError < ActiveRecordError #:nodoc:
-
1
def initialize(owner_class_name, reflection)
-
super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.")
-
end
-
end
-
-
1
class HasManyThroughAssociationPointlessSourceTypeError < ActiveRecordError #:nodoc:
-
1
def initialize(owner_class_name, reflection, source_reflection)
-
super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' with a :source_type option if the '#{reflection.through_reflection.class_name}##{source_reflection.name}' is not polymorphic. Try removing :source_type on your association.")
-
end
-
end
-
-
1
class HasOneThroughCantAssociateThroughCollection < ActiveRecordError #:nodoc:
-
1
def initialize(owner_class_name, reflection, through_reflection)
-
super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' where the :through association '#{owner_class_name}##{through_reflection.name}' is a collection. Specify a has_one or belongs_to association in the :through option instead.")
-
end
-
end
-
-
1
class HasOneAssociationPolymorphicThroughError < ActiveRecordError #:nodoc:
-
1
def initialize(owner_class_name, reflection)
-
super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.")
-
end
-
end
-
-
1
class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc:
-
1
def initialize(reflection)
-
through_reflection = reflection.through_reflection
-
source_reflection_names = reflection.source_reflection_names
-
source_associations = reflection.through_reflection.klass._reflections.keys
-
super("Could not find the source association(s) #{source_reflection_names.collect{ |a| a.inspect }.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)}?")
-
end
-
end
-
-
1
class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc:
-
1
def initialize(owner, reflection)
-
super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.")
-
end
-
end
-
-
1
class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc:
-
1
def initialize(owner, reflection)
-
super("Cannot associate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to create the has_many :through record associating them.")
-
end
-
end
-
-
1
class HasManyThroughCantDissociateNewRecords < ActiveRecordError #:nodoc:
-
1
def initialize(owner, reflection)
-
super("Cannot dissociate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to delete the has_many :through record associating them.")
-
end
-
end
-
-
1
class HasManyThroughNestedAssociationsAreReadonly < ActiveRecordError #:nodoc:
-
1
def initialize(owner, reflection)
-
super("Cannot modify association '#{owner.class.name}##{reflection.name}' because it goes through more than one other association.")
-
end
-
end
-
-
1
class EagerLoadPolymorphicError < ActiveRecordError #:nodoc:
-
1
def initialize(reflection)
-
super("Cannot eagerly load the polymorphic association #{reflection.name.inspect}")
-
end
-
end
-
-
1
class ReadOnlyAssociation < ActiveRecordError #:nodoc:
-
1
def initialize(reflection)
-
super("Cannot add to a has_many :through association. Try adding to #{reflection.through_reflection.name.inspect}.")
-
end
-
end
-
-
# This error is raised when trying to destroy a parent instance in N:1 or 1:1 associations
-
# (has_many, has_one) when there is at least 1 child associated instance.
-
# ex: if @project.tasks.size > 0, DeleteRestrictionError will be raised when trying to destroy @project
-
1
class DeleteRestrictionError < ActiveRecordError #:nodoc:
-
1
def initialize(name)
-
super("Cannot delete record because of dependent #{name}")
-
end
-
end
-
-
# See ActiveRecord::Associations::ClassMethods for documentation.
-
1
module Associations # :nodoc:
-
1
extend ActiveSupport::Autoload
-
1
extend ActiveSupport::Concern
-
-
# These classes will be loaded when associations are created.
-
# So there is no need to eager load them.
-
1
autoload :Association, 'active_record/associations/association'
-
1
autoload :SingularAssociation, 'active_record/associations/singular_association'
-
1
autoload :CollectionAssociation, 'active_record/associations/collection_association'
-
1
autoload :ForeignAssociation, 'active_record/associations/foreign_association'
-
1
autoload :CollectionProxy, 'active_record/associations/collection_proxy'
-
-
1
autoload :BelongsToAssociation, 'active_record/associations/belongs_to_association'
-
1
autoload :BelongsToPolymorphicAssociation, 'active_record/associations/belongs_to_polymorphic_association'
-
1
autoload :HasManyAssociation, 'active_record/associations/has_many_association'
-
1
autoload :HasManyThroughAssociation, 'active_record/associations/has_many_through_association'
-
1
autoload :HasOneAssociation, 'active_record/associations/has_one_association'
-
1
autoload :HasOneThroughAssociation, 'active_record/associations/has_one_through_association'
-
1
autoload :ThroughAssociation, 'active_record/associations/through_association'
-
-
1
module Builder #:nodoc:
-
1
autoload :Association, 'active_record/associations/builder/association'
-
1
autoload :SingularAssociation, 'active_record/associations/builder/singular_association'
-
1
autoload :CollectionAssociation, 'active_record/associations/builder/collection_association'
-
-
1
autoload :BelongsTo, 'active_record/associations/builder/belongs_to'
-
1
autoload :HasOne, 'active_record/associations/builder/has_one'
-
1
autoload :HasMany, 'active_record/associations/builder/has_many'
-
1
autoload :HasAndBelongsToMany, 'active_record/associations/builder/has_and_belongs_to_many'
-
end
-
-
1
eager_autoload do
-
1
autoload :Preloader, 'active_record/associations/preloader'
-
1
autoload :JoinDependency, 'active_record/associations/join_dependency'
-
1
autoload :AssociationScope, 'active_record/associations/association_scope'
-
1
autoload :AliasTracker, 'active_record/associations/alias_tracker'
-
end
-
-
# Clears out the association cache.
-
1
def clear_association_cache #:nodoc:
-
@association_cache.clear if persisted?
-
end
-
-
# :nodoc:
-
1
attr_reader :association_cache
-
-
# Returns the association instance for the given name, instantiating it if it doesn't already exist
-
1
def association(name) #:nodoc:
-
association = association_instance_get(name)
-
-
if association.nil?
-
raise AssociationNotFoundError.new(self, name) unless reflection = self.class._reflect_on_association(name)
-
association = reflection.association_class.new(self, reflection)
-
association_instance_set(name, association)
-
end
-
-
association
-
end
-
-
1
private
-
# Returns the specified association instance if it responds to :loaded?, nil otherwise.
-
1
def association_instance_get(name)
-
60
@association_cache[name]
-
end
-
-
# Set the specified association instance.
-
1
def association_instance_set(name, association)
-
@association_cache[name] = association
-
end
-
-
# \Associations are a set of macro-like class methods for tying objects together through
-
# foreign keys. They express relationships like "Project has one Project Manager"
-
# or "Project belongs to a Portfolio". Each macro adds a number of methods to the
-
# class which are specialized according to the collection or association symbol and the
-
# options hash. It works much the same way as Ruby's own <tt>attr*</tt>
-
# methods.
-
#
-
# class Project < ActiveRecord::Base
-
# belongs_to :portfolio
-
# has_one :project_manager
-
# has_many :milestones
-
# has_and_belongs_to_many :categories
-
# end
-
#
-
# The project class now has the following methods (and more) to ease the traversal and
-
# manipulation of its relationships:
-
# * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt>
-
# * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt>
-
# * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt>
-
# <tt>Project#milestones.delete(milestone), Project#milestones.destroy(milestone), Project#milestones.find(milestone_id),</tt>
-
# <tt>Project#milestones.build, Project#milestones.create</tt>
-
# * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt>
-
# <tt>Project#categories.delete(category1), Project#categories.destroy(category1)</tt>
-
#
-
# === A word of warning
-
#
-
# Don't create associations that have the same name as instance methods of
-
# <tt>ActiveRecord::Base</tt>. Since the association adds a method with that name to
-
# its model, it will override the inherited method and break things.
-
# For instance, +attributes+ and +connection+ would be bad choices for association names.
-
#
-
# == Auto-generated methods
-
# See also Instance Public methods below for more details.
-
#
-
# === Singular associations (one-to-one)
-
# | | belongs_to |
-
# generated methods | belongs_to | :polymorphic | has_one
-
# ----------------------------------+------------+--------------+---------
-
# other(force_reload=false) | X | X | X
-
# other=(other) | X | X | X
-
# build_other(attributes={}) | X | | X
-
# create_other(attributes={}) | X | | X
-
# create_other!(attributes={}) | X | | X
-
#
-
# ===Collection associations (one-to-many / many-to-many)
-
# | | | has_many
-
# generated methods | habtm | has_many | :through
-
# ----------------------------------+-------+----------+----------
-
# others(force_reload=false) | X | X | X
-
# others=(other,other,...) | X | X | X
-
# other_ids | X | X | X
-
# other_ids=(id,id,...) | X | X | X
-
# others<< | X | X | X
-
# others.push | X | X | X
-
# others.concat | X | X | X
-
# others.build(attributes={}) | X | X | X
-
# others.create(attributes={}) | X | X | X
-
# others.create!(attributes={}) | X | X | X
-
# others.size | X | X | X
-
# others.length | X | X | X
-
# others.count | X | X | X
-
# others.sum(*args) | X | X | X
-
# others.empty? | X | X | X
-
# others.clear | X | X | X
-
# others.delete(other,other,...) | X | X | X
-
# others.delete_all | X | X | X
-
# others.destroy(other,other,...) | X | X | X
-
# others.destroy_all | X | X | X
-
# others.find(*args) | X | X | X
-
# others.exists? | X | X | X
-
# others.distinct | X | X | X
-
# others.uniq | X | X | X
-
# others.reset | X | X | X
-
#
-
# === Overriding generated methods
-
#
-
# Association methods are generated in a module that is included into the model class,
-
# which allows you to easily override with your own methods and call the original
-
# generated method with +super+. For example:
-
#
-
# class Car < ActiveRecord::Base
-
# belongs_to :owner
-
# belongs_to :old_owner
-
# def owner=(new_owner)
-
# self.old_owner = self.owner
-
# super
-
# end
-
# end
-
#
-
# If your model class is <tt>Project</tt>, the module is
-
# named <tt>Project::GeneratedFeatureMethods</tt>. The GeneratedFeatureMethods module is
-
# included in the model class immediately after the (anonymous) generated attributes methods
-
# module, meaning an association will override the methods for an attribute with the same name.
-
#
-
# == Cardinality and associations
-
#
-
# Active Record associations can be used to describe one-to-one, one-to-many and many-to-many
-
# relationships between models. Each model uses an association to describe its role in
-
# the relation. The +belongs_to+ association is always used in the model that has
-
# the foreign key.
-
#
-
# === One-to-one
-
#
-
# Use +has_one+ in the base, and +belongs_to+ in the associated model.
-
#
-
# class Employee < ActiveRecord::Base
-
# has_one :office
-
# end
-
# class Office < ActiveRecord::Base
-
# belongs_to :employee # foreign key - employee_id
-
# end
-
#
-
# === One-to-many
-
#
-
# Use +has_many+ in the base, and +belongs_to+ in the associated model.
-
#
-
# class Manager < ActiveRecord::Base
-
# has_many :employees
-
# end
-
# class Employee < ActiveRecord::Base
-
# belongs_to :manager # foreign key - manager_id
-
# end
-
#
-
# === Many-to-many
-
#
-
# There are two ways to build a many-to-many relationship.
-
#
-
# The first way uses a +has_many+ association with the <tt>:through</tt> option and a join model, so
-
# there are two stages of associations.
-
#
-
# class Assignment < ActiveRecord::Base
-
# belongs_to :programmer # foreign key - programmer_id
-
# belongs_to :project # foreign key - project_id
-
# end
-
# class Programmer < ActiveRecord::Base
-
# has_many :assignments
-
# has_many :projects, through: :assignments
-
# end
-
# class Project < ActiveRecord::Base
-
# has_many :assignments
-
# has_many :programmers, through: :assignments
-
# end
-
#
-
# For the second way, use +has_and_belongs_to_many+ in both models. This requires a join table
-
# that has no corresponding model or primary key.
-
#
-
# class Programmer < ActiveRecord::Base
-
# has_and_belongs_to_many :projects # foreign keys in the join table
-
# end
-
# class Project < ActiveRecord::Base
-
# has_and_belongs_to_many :programmers # foreign keys in the join table
-
# end
-
#
-
# Choosing which way to build a many-to-many relationship is not always simple.
-
# If you need to work with the relationship model as its own entity,
-
# use <tt>has_many :through</tt>. Use +has_and_belongs_to_many+ when working with legacy schemas or when
-
# you never work directly with the relationship itself.
-
#
-
# == Is it a +belongs_to+ or +has_one+ association?
-
#
-
# Both express a 1-1 relationship. The difference is mostly where to place the foreign
-
# key, which goes on the table for the class declaring the +belongs_to+ relationship.
-
#
-
# class User < ActiveRecord::Base
-
# # I reference an account.
-
# belongs_to :account
-
# end
-
#
-
# class Account < ActiveRecord::Base
-
# # One user references me.
-
# has_one :user
-
# end
-
#
-
# The tables for these classes could look something like:
-
#
-
# CREATE TABLE users (
-
# id int(11) NOT NULL auto_increment,
-
# account_id int(11) default NULL,
-
# name varchar default NULL,
-
# PRIMARY KEY (id)
-
# )
-
#
-
# CREATE TABLE accounts (
-
# id int(11) NOT NULL auto_increment,
-
# name varchar default NULL,
-
# PRIMARY KEY (id)
-
# )
-
#
-
# == Unsaved objects and associations
-
#
-
# You can manipulate objects and associations before they are saved to the database, but
-
# there is some special behavior you should be aware of, mostly involving the saving of
-
# associated objects.
-
#
-
# You can set the <tt>:autosave</tt> option on a <tt>has_one</tt>, <tt>belongs_to</tt>,
-
# <tt>has_many</tt>, or <tt>has_and_belongs_to_many</tt> association. Setting it
-
# to +true+ will _always_ save the members, whereas setting it to +false+ will
-
# _never_ save the members. More details about <tt>:autosave</tt> option is available at
-
# AutosaveAssociation.
-
#
-
# === One-to-one associations
-
#
-
# * Assigning an object to a +has_one+ association automatically saves that object and
-
# the object being replaced (if there is one), in order to update their foreign
-
# keys - except if the parent object is unsaved (<tt>new_record? == true</tt>).
-
# * If either of these saves fail (due to one of the objects being invalid), an
-
# <tt>ActiveRecord::RecordNotSaved</tt> exception is raised and the assignment is
-
# cancelled.
-
# * If you wish to assign an object to a +has_one+ association without saving it,
-
# use the <tt>build_association</tt> method (documented below). The object being
-
# replaced will still be saved to update its foreign key.
-
# * Assigning an object to a +belongs_to+ association does not save the object, since
-
# the foreign key field belongs on the parent. It does not save the parent either.
-
#
-
# === Collections
-
#
-
# * Adding an object to a collection (+has_many+ or +has_and_belongs_to_many+) automatically
-
# saves that object, except if the parent object (the owner of the collection) is not yet
-
# stored in the database.
-
# * If saving any of the objects being added to a collection (via <tt>push</tt> or similar)
-
# fails, then <tt>push</tt> returns +false+.
-
# * If saving fails while replacing the collection (via <tt>association=</tt>), an
-
# <tt>ActiveRecord::RecordNotSaved</tt> exception is raised and the assignment is
-
# cancelled.
-
# * You can add an object to a collection without automatically saving it by using the
-
# <tt>collection.build</tt> method (documented below).
-
# * All unsaved (<tt>new_record? == true</tt>) members of the collection are automatically
-
# saved when the parent is saved.
-
#
-
# == Customizing the query
-
#
-
# \Associations are built from <tt>Relation</tt>s, and you can use the <tt>Relation</tt> syntax
-
# to customize them. For example, to add a condition:
-
#
-
# class Blog < ActiveRecord::Base
-
# has_many :published_posts, -> { where published: true }, class_name: 'Post'
-
# end
-
#
-
# Inside the <tt>-> { ... }</tt> block you can use all of the usual <tt>Relation</tt> methods.
-
#
-
# === Accessing the owner object
-
#
-
# Sometimes it is useful to have access to the owner object when building the query. The owner
-
# is passed as a parameter to the block. For example, the following association would find all
-
# events that occur on the user's birthday:
-
#
-
# class User < ActiveRecord::Base
-
# has_many :birthday_events, ->(user) { where starts_on: user.birthday }, class_name: 'Event'
-
# end
-
#
-
# Note: Joining, eager loading and preloading of these associations is not fully possible.
-
# These operations happen before instance creation and the scope will be called with a +nil+ argument.
-
# This can lead to unexpected behavior and is deprecated.
-
#
-
# == Association callbacks
-
#
-
# Similar to the normal callbacks that hook into the life cycle of an Active Record object,
-
# you can also define callbacks that get triggered when you add an object to or remove an
-
# object from an association collection.
-
#
-
# class Project
-
# has_and_belongs_to_many :developers, after_add: :evaluate_velocity
-
#
-
# def evaluate_velocity(developer)
-
# ...
-
# end
-
# end
-
#
-
# It's possible to stack callbacks by passing them as an array. Example:
-
#
-
# class Project
-
# has_and_belongs_to_many :developers,
-
# after_add: [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}]
-
# end
-
#
-
# Possible callbacks are: +before_add+, +after_add+, +before_remove+ and +after_remove+.
-
#
-
# If any of the +before_add+ callbacks throw an exception, the object will not be
-
# added to the collection.
-
#
-
# Similarly, if any of the +before_remove+ callbacks throw an exception, the object
-
# will not be removed from the collection.
-
#
-
# == Association extensions
-
#
-
# The proxy objects that control the access to associations can be extended through anonymous
-
# modules. This is especially beneficial for adding new finders, creators, and other
-
# factory-type methods that are only used as part of this association.
-
#
-
# class Account < ActiveRecord::Base
-
# has_many :people do
-
# def find_or_create_by_name(name)
-
# first_name, last_name = name.split(" ", 2)
-
# find_or_create_by(first_name: first_name, last_name: last_name)
-
# end
-
# end
-
# end
-
#
-
# person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson")
-
# person.first_name # => "David"
-
# person.last_name # => "Heinemeier Hansson"
-
#
-
# If you need to share the same extensions between many associations, you can use a named
-
# extension module.
-
#
-
# module FindOrCreateByNameExtension
-
# def find_or_create_by_name(name)
-
# first_name, last_name = name.split(" ", 2)
-
# find_or_create_by(first_name: first_name, last_name: last_name)
-
# end
-
# end
-
#
-
# class Account < ActiveRecord::Base
-
# has_many :people, -> { extending FindOrCreateByNameExtension }
-
# end
-
#
-
# class Company < ActiveRecord::Base
-
# has_many :people, -> { extending FindOrCreateByNameExtension }
-
# end
-
#
-
# Some extensions can only be made to work with knowledge of the association's internals.
-
# Extensions can access relevant state using the following methods (where +items+ is the
-
# name of the association):
-
#
-
# * <tt>record.association(:items).owner</tt> - Returns the object the association is part of.
-
# * <tt>record.association(:items).reflection</tt> - Returns the reflection object that describes the association.
-
# * <tt>record.association(:items).target</tt> - Returns the associated object for +belongs_to+ and +has_one+, or
-
# the collection of associated objects for +has_many+ and +has_and_belongs_to_many+.
-
#
-
# However, inside the actual extension code, you will not have access to the <tt>record</tt> as
-
# above. In this case, you can access <tt>proxy_association</tt>. For example,
-
# <tt>record.association(:items)</tt> and <tt>record.items.proxy_association</tt> will return
-
# the same object, allowing you to make calls like <tt>proxy_association.owner</tt> inside
-
# association extensions.
-
#
-
# == Association Join Models
-
#
-
# Has Many associations can be configured with the <tt>:through</tt> option to use an
-
# explicit join model to retrieve the data. This operates similarly to a
-
# +has_and_belongs_to_many+ association. The advantage is that you're able to add validations,
-
# callbacks, and extra attributes on the join model. Consider the following schema:
-
#
-
# class Author < ActiveRecord::Base
-
# has_many :authorships
-
# has_many :books, through: :authorships
-
# end
-
#
-
# class Authorship < ActiveRecord::Base
-
# belongs_to :author
-
# belongs_to :book
-
# end
-
#
-
# @author = Author.first
-
# @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to
-
# @author.books # selects all books by using the Authorship join model
-
#
-
# You can also go through a +has_many+ association on the join model:
-
#
-
# class Firm < ActiveRecord::Base
-
# has_many :clients
-
# has_many :invoices, through: :clients
-
# end
-
#
-
# class Client < ActiveRecord::Base
-
# belongs_to :firm
-
# has_many :invoices
-
# end
-
#
-
# class Invoice < ActiveRecord::Base
-
# belongs_to :client
-
# end
-
#
-
# @firm = Firm.first
-
# @firm.clients.flat_map { |c| c.invoices } # select all invoices for all clients of the firm
-
# @firm.invoices # selects all invoices by going through the Client join model
-
#
-
# Similarly you can go through a +has_one+ association on the join model:
-
#
-
# class Group < ActiveRecord::Base
-
# has_many :users
-
# has_many :avatars, through: :users
-
# end
-
#
-
# class User < ActiveRecord::Base
-
# belongs_to :group
-
# has_one :avatar
-
# end
-
#
-
# class Avatar < ActiveRecord::Base
-
# belongs_to :user
-
# end
-
#
-
# @group = Group.first
-
# @group.users.collect { |u| u.avatar }.compact # select all avatars for all users in the group
-
# @group.avatars # selects all avatars by going through the User join model.
-
#
-
# An important caveat with going through +has_one+ or +has_many+ associations on the
-
# join model is that these associations are *read-only*. For example, the following
-
# would not work following the previous example:
-
#
-
# @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around
-
# @group.avatars.delete(@group.avatars.last) # so would this
-
#
-
# == Setting Inverses
-
#
-
# If you are using a +belongs_to+ on the join model, it is a good idea to set the
-
# <tt>:inverse_of</tt> option on the +belongs_to+, which will mean that the following example
-
# works correctly (where <tt>tags</tt> is a +has_many+ <tt>:through</tt> association):
-
#
-
# @post = Post.first
-
# @tag = @post.tags.build name: "ruby"
-
# @tag.save
-
#
-
# The last line ought to save the through record (a <tt>Taggable</tt>). This will only work if the
-
# <tt>:inverse_of</tt> is set:
-
#
-
# class Taggable < ActiveRecord::Base
-
# belongs_to :post
-
# belongs_to :tag, inverse_of: :taggings
-
# end
-
#
-
# If you do not set the <tt>:inverse_of</tt> record, the association will
-
# do its best to match itself up with the correct inverse. Automatic
-
# inverse detection only works on <tt>has_many</tt>, <tt>has_one</tt>, and
-
# <tt>belongs_to</tt> associations.
-
#
-
# Extra options on the associations, as defined in the
-
# <tt>AssociationReflection::INVALID_AUTOMATIC_INVERSE_OPTIONS</tt> constant, will
-
# also prevent the association's inverse from being found automatically.
-
#
-
# The automatic guessing of the inverse association uses a heuristic based
-
# on the name of the class, so it may not work for all associations,
-
# especially the ones with non-standard names.
-
#
-
# You can turn off the automatic detection of inverse associations by setting
-
# the <tt>:inverse_of</tt> option to <tt>false</tt> like so:
-
#
-
# class Taggable < ActiveRecord::Base
-
# belongs_to :tag, inverse_of: false
-
# end
-
#
-
# == Nested \Associations
-
#
-
# You can actually specify *any* association with the <tt>:through</tt> option, including an
-
# association which has a <tt>:through</tt> option itself. For example:
-
#
-
# class Author < ActiveRecord::Base
-
# has_many :posts
-
# has_many :comments, through: :posts
-
# has_many :commenters, through: :comments
-
# end
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :comments
-
# end
-
#
-
# class Comment < ActiveRecord::Base
-
# belongs_to :commenter
-
# end
-
#
-
# @author = Author.first
-
# @author.commenters # => People who commented on posts written by the author
-
#
-
# An equivalent way of setting up this association this would be:
-
#
-
# class Author < ActiveRecord::Base
-
# has_many :posts
-
# has_many :commenters, through: :posts
-
# end
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :comments
-
# has_many :commenters, through: :comments
-
# end
-
#
-
# class Comment < ActiveRecord::Base
-
# belongs_to :commenter
-
# end
-
#
-
# When using a nested association, you will not be able to modify the association because there
-
# is not enough information to know what modification to make. For example, if you tried to
-
# add a <tt>Commenter</tt> in the example above, there would be no way to tell how to set up the
-
# intermediate <tt>Post</tt> and <tt>Comment</tt> objects.
-
#
-
# == Polymorphic \Associations
-
#
-
# Polymorphic associations on models are not restricted on what types of models they
-
# can be associated with. Rather, they specify an interface that a +has_many+ association
-
# must adhere to.
-
#
-
# class Asset < ActiveRecord::Base
-
# belongs_to :attachable, polymorphic: true
-
# end
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :assets, as: :attachable # The :as option specifies the polymorphic interface to use.
-
# end
-
#
-
# @asset.attachable = @post
-
#
-
# This works by using a type column in addition to a foreign key to specify the associated
-
# record. In the Asset example, you'd need an +attachable_id+ integer column and an
-
# +attachable_type+ string column.
-
#
-
# Using polymorphic associations in combination with single table inheritance (STI) is
-
# a little tricky. In order for the associations to work as expected, ensure that you
-
# store the base model for the STI models in the type column of the polymorphic
-
# association. To continue with the asset example above, suppose there are guest posts
-
# and member posts that use the posts table for STI. In this case, there must be a +type+
-
# column in the posts table.
-
#
-
# Note: The <tt>attachable_type=</tt> method is being called when assigning an +attachable+.
-
# The +class_name+ of the +attachable+ is passed as a String.
-
#
-
# class Asset < ActiveRecord::Base
-
# belongs_to :attachable, polymorphic: true
-
#
-
# def attachable_type=(class_name)
-
# super(class_name.constantize.base_class.to_s)
-
# end
-
# end
-
#
-
# class Post < ActiveRecord::Base
-
# # because we store "Post" in attachable_type now dependent: :destroy will work
-
# has_many :assets, as: :attachable, dependent: :destroy
-
# end
-
#
-
# class GuestPost < Post
-
# end
-
#
-
# class MemberPost < Post
-
# end
-
#
-
# == Caching
-
#
-
# All of the methods are built on a simple caching principle that will keep the result
-
# of the last query around unless specifically instructed not to. The cache is even
-
# shared across methods to make it even cheaper to use the macro-added methods without
-
# worrying too much about performance at the first go.
-
#
-
# project.milestones # fetches milestones from the database
-
# project.milestones.size # uses the milestone cache
-
# project.milestones.empty? # uses the milestone cache
-
# project.milestones(true).size # fetches milestones from the database
-
# project.milestones # uses the milestone cache
-
#
-
# == Eager loading of associations
-
#
-
# Eager loading is a way to find objects of a certain class and a number of named associations.
-
# It is one of the easiest ways to prevent the dreaded N+1 problem in which fetching 100
-
# posts that each need to display their author triggers 101 database queries. Through the
-
# use of eager loading, the number of queries will be reduced from 101 to 2.
-
#
-
# class Post < ActiveRecord::Base
-
# belongs_to :author
-
# has_many :comments
-
# end
-
#
-
# Consider the following loop using the class above:
-
#
-
# Post.all.each do |post|
-
# puts "Post: " + post.title
-
# puts "Written by: " + post.author.name
-
# puts "Last comment on: " + post.comments.first.created_on
-
# end
-
#
-
# To iterate over these one hundred posts, we'll generate 201 database queries. Let's
-
# first just optimize it for retrieving the author:
-
#
-
# Post.includes(:author).each do |post|
-
#
-
# This references the name of the +belongs_to+ association that also used the <tt>:author</tt>
-
# symbol. After loading the posts, find will collect the +author_id+ from each one and load
-
# all the referenced authors with one query. Doing so will cut down the number of queries
-
# from 201 to 102.
-
#
-
# We can improve upon the situation further by referencing both associations in the finder with:
-
#
-
# Post.includes(:author, :comments).each do |post|
-
#
-
# This will load all comments with a single query. This reduces the total number of queries
-
# to 3. In general, the number of queries will be 1 plus the number of associations
-
# named (except if some of the associations are polymorphic +belongs_to+ - see below).
-
#
-
# To include a deep hierarchy of associations, use a hash:
-
#
-
# Post.includes(:author, { comments: { author: :gravatar } }).each do |post|
-
#
-
# The above code will load all the comments and all of their associated
-
# authors and gravatars. You can mix and match any combination of symbols,
-
# arrays, and hashes to retrieve the associations you want to load.
-
#
-
# All of this power shouldn't fool you into thinking that you can pull out huge amounts
-
# of data with no performance penalty just because you've reduced the number of queries.
-
# The database still needs to send all the data to Active Record and it still needs to
-
# be processed. So it's no catch-all for performance problems, but it's a great way to
-
# cut down on the number of queries in a situation as the one described above.
-
#
-
# Since only one table is loaded at a time, conditions or orders cannot reference tables
-
# other than the main one. If this is the case, Active Record falls back to the previously
-
# used LEFT OUTER JOIN based strategy. For example:
-
#
-
# Post.includes([:author, :comments]).where(['comments.approved = ?', true])
-
#
-
# This will result in a single SQL query with joins along the lines of:
-
# <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt> and
-
# <tt>LEFT OUTER JOIN authors ON authors.id = posts.author_id</tt>. Note that using conditions
-
# like this can have unintended consequences.
-
# In the above example posts with no approved comments are not returned at all, because
-
# the conditions apply to the SQL statement as a whole and not just to the association.
-
#
-
# You must disambiguate column references for this fallback to happen, for example
-
# <tt>order: "author.name DESC"</tt> will work but <tt>order: "name DESC"</tt> will not.
-
#
-
# If you want to load all posts (including posts with no approved comments) then write
-
# your own LEFT OUTER JOIN query using ON
-
#
-
# Post.joins("LEFT OUTER JOIN comments ON comments.post_id = posts.id AND comments.approved = '1'")
-
#
-
# In this case it is usually more natural to include an association which has conditions defined on it:
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :approved_comments, -> { where approved: true }, class_name: 'Comment'
-
# end
-
#
-
# Post.includes(:approved_comments)
-
#
-
# This will load posts and eager load the +approved_comments+ association, which contains
-
# only those comments that have been approved.
-
#
-
# If you eager load an association with a specified <tt>:limit</tt> option, it will be ignored,
-
# returning all the associated objects:
-
#
-
# class Picture < ActiveRecord::Base
-
# has_many :most_recent_comments, -> { order('id DESC').limit(10) }, class_name: 'Comment'
-
# end
-
#
-
# Picture.includes(:most_recent_comments).first.most_recent_comments # => returns all associated comments.
-
#
-
# Eager loading is supported with polymorphic associations.
-
#
-
# class Address < ActiveRecord::Base
-
# belongs_to :addressable, polymorphic: true
-
# end
-
#
-
# A call that tries to eager load the addressable model
-
#
-
# Address.includes(:addressable)
-
#
-
# This will execute one query to load the addresses and load the addressables with one
-
# query per addressable type.
-
# For example if all the addressables are either of class Person or Company then a total
-
# of 3 queries will be executed. The list of addressable types to load is determined on
-
# the back of the addresses loaded. This is not supported if Active Record has to fallback
-
# to the previous implementation of eager loading and will raise <tt>ActiveRecord::EagerLoadPolymorphicError</tt>.
-
# The reason is that the parent model's type is a column value so its corresponding table
-
# name cannot be put in the +FROM+/+JOIN+ clauses of that query.
-
#
-
# == Table Aliasing
-
#
-
# Active Record uses table aliasing in the case that a table is referenced multiple times
-
# in a join. If a table is referenced only once, the standard table name is used. The
-
# second time, the table is aliased as <tt>#{reflection_name}_#{parent_table_name}</tt>.
-
# Indexes are appended for any more successive uses of the table name.
-
#
-
# Post.joins(:comments)
-
# # => SELECT ... FROM posts INNER JOIN comments ON ...
-
# Post.joins(:special_comments) # STI
-
# # => SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment'
-
# Post.joins(:comments, :special_comments) # special_comments is the reflection name, posts is the parent table name
-
# # => SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts
-
#
-
# Acts as tree example:
-
#
-
# TreeMixin.joins(:children)
-
# # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
-
# TreeMixin.joins(children: :parent)
-
# # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
-
# INNER JOIN parents_mixins ...
-
# TreeMixin.joins(children: {parent: :children})
-
# # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
-
# INNER JOIN parents_mixins ...
-
# INNER JOIN mixins childrens_mixins_2
-
#
-
# Has and Belongs to Many join tables use the same idea, but add a <tt>_join</tt> suffix:
-
#
-
# Post.joins(:categories)
-
# # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
-
# Post.joins(categories: :posts)
-
# # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
-
# INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
-
# Post.joins(categories: {posts: :categories})
-
# # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
-
# INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
-
# INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2
-
#
-
# If you wish to specify your own custom joins using <tt>joins</tt> method, those table
-
# names will take precedence over the eager associations:
-
#
-
# Post.joins(:comments).joins("inner join comments ...")
-
# # => SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ...
-
# Post.joins(:comments, :special_comments).joins("inner join comments ...")
-
# # => SELECT ... FROM posts INNER JOIN comments comments_posts ON ...
-
# INNER JOIN comments special_comments_posts ...
-
# INNER JOIN comments ...
-
#
-
# Table aliases are automatically truncated according to the maximum length of table identifiers
-
# according to the specific database.
-
#
-
# == Modules
-
#
-
# By default, associations will look for objects within the current module scope. Consider:
-
#
-
# module MyApplication
-
# module Business
-
# class Firm < ActiveRecord::Base
-
# has_many :clients
-
# end
-
#
-
# class Client < ActiveRecord::Base; end
-
# end
-
# end
-
#
-
# When <tt>Firm#clients</tt> is called, it will in turn call
-
# <tt>MyApplication::Business::Client.find_all_by_firm_id(firm.id)</tt>.
-
# If you want to associate with a class in another module scope, this can be done by
-
# specifying the complete class name.
-
#
-
# module MyApplication
-
# module Business
-
# class Firm < ActiveRecord::Base; end
-
# end
-
#
-
# module Billing
-
# class Account < ActiveRecord::Base
-
# belongs_to :firm, class_name: "MyApplication::Business::Firm"
-
# end
-
# end
-
# end
-
#
-
# == Bi-directional associations
-
#
-
# When you specify an association there is usually an association on the associated model
-
# that specifies the same relationship in reverse. For example, with the following models:
-
#
-
# class Dungeon < ActiveRecord::Base
-
# has_many :traps
-
# has_one :evil_wizard
-
# end
-
#
-
# class Trap < ActiveRecord::Base
-
# belongs_to :dungeon
-
# end
-
#
-
# class EvilWizard < ActiveRecord::Base
-
# belongs_to :dungeon
-
# end
-
#
-
# The +traps+ association on +Dungeon+ and the +dungeon+ association on +Trap+ are
-
# the inverse of each other and the inverse of the +dungeon+ association on +EvilWizard+
-
# is the +evil_wizard+ association on +Dungeon+ (and vice-versa). By default,
-
# Active Record doesn't know anything about these inverse relationships and so no object
-
# loading optimization is possible. For example:
-
#
-
# d = Dungeon.first
-
# t = d.traps.first
-
# d.level == t.dungeon.level # => true
-
# d.level = 10
-
# d.level == t.dungeon.level # => false
-
#
-
# The +Dungeon+ instances +d+ and <tt>t.dungeon</tt> in the above example refer to
-
# the same object data from the database, but are actually different in-memory copies
-
# of that data. Specifying the <tt>:inverse_of</tt> option on associations lets you tell
-
# Active Record about inverse relationships and it will optimise object loading. For
-
# example, if we changed our model definitions to:
-
#
-
# class Dungeon < ActiveRecord::Base
-
# has_many :traps, inverse_of: :dungeon
-
# has_one :evil_wizard, inverse_of: :dungeon
-
# end
-
#
-
# class Trap < ActiveRecord::Base
-
# belongs_to :dungeon, inverse_of: :traps
-
# end
-
#
-
# class EvilWizard < ActiveRecord::Base
-
# belongs_to :dungeon, inverse_of: :evil_wizard
-
# end
-
#
-
# Then, from our code snippet above, +d+ and <tt>t.dungeon</tt> are actually the same
-
# in-memory instance and our final <tt>d.level == t.dungeon.level</tt> will return +true+.
-
#
-
# There are limitations to <tt>:inverse_of</tt> support:
-
#
-
# * does not work with <tt>:through</tt> associations.
-
# * does not work with <tt>:polymorphic</tt> associations.
-
# * for +belongs_to+ associations +has_many+ inverse associations are ignored.
-
#
-
# == Deleting from associations
-
#
-
# === Dependent associations
-
#
-
# +has_many+, +has_one+ and +belongs_to+ associations support the <tt>:dependent</tt> option.
-
# This allows you to specify that associated records should be deleted when the owner is
-
# deleted.
-
#
-
# For example:
-
#
-
# class Author
-
# has_many :posts, dependent: :destroy
-
# end
-
# Author.find(1).destroy # => Will destroy all of the author's posts, too
-
#
-
# The <tt>:dependent</tt> option can have different values which specify how the deletion
-
# is done. For more information, see the documentation for this option on the different
-
# specific association types. When no option is given, the behavior is to do nothing
-
# with the associated records when destroying a record.
-
#
-
# Note that <tt>:dependent</tt> is implemented using Rails' callback
-
# system, which works by processing callbacks in order. Therefore, other
-
# callbacks declared either before or after the <tt>:dependent</tt> option
-
# can affect what it does.
-
#
-
# === Delete or destroy?
-
#
-
# +has_many+ and +has_and_belongs_to_many+ associations have the methods <tt>destroy</tt>,
-
# <tt>delete</tt>, <tt>destroy_all</tt> and <tt>delete_all</tt>.
-
#
-
# For +has_and_belongs_to_many+, <tt>delete</tt> and <tt>destroy</tt> are the same: they
-
# cause the records in the join table to be removed.
-
#
-
# For +has_many+, <tt>destroy</tt> and <tt>destroy_all</tt> will always call the <tt>destroy</tt> method of the
-
# record(s) being removed so that callbacks are run. However <tt>delete</tt> and <tt>delete_all</tt> will either
-
# do the deletion according to the strategy specified by the <tt>:dependent</tt> option, or
-
# if no <tt>:dependent</tt> option is given, then it will follow the default strategy.
-
# The default strategy is to do nothing (leave the foreign keys with the parent ids set), except for
-
# +has_many+ <tt>:through</tt>, where the default strategy is <tt>delete_all</tt> (delete
-
# the join records, without running their callbacks).
-
#
-
# There is also a <tt>clear</tt> method which is the same as <tt>delete_all</tt>, except that
-
# it returns the association rather than the records which have been deleted.
-
#
-
# === What gets deleted?
-
#
-
# There is a potential pitfall here: +has_and_belongs_to_many+ and +has_many+ <tt>:through</tt>
-
# associations have records in join tables, as well as the associated records. So when we
-
# call one of these deletion methods, what exactly should be deleted?
-
#
-
# The answer is that it is assumed that deletion on an association is about removing the
-
# <i>link</i> between the owner and the associated object(s), rather than necessarily the
-
# associated objects themselves. So with +has_and_belongs_to_many+ and +has_many+
-
# <tt>:through</tt>, the join records will be deleted, but the associated records won't.
-
#
-
# This makes sense if you think about it: if you were to call <tt>post.tags.delete(Tag.find_by(name: 'food'))</tt>
-
# you would want the 'food' tag to be unlinked from the post, rather than for the tag itself
-
# to be removed from the database.
-
#
-
# However, there are examples where this strategy doesn't make sense. For example, suppose
-
# a person has many projects, and each project has many tasks. If we deleted one of a person's
-
# tasks, we would probably not want the project to be deleted. In this scenario, the delete method
-
# won't actually work: it can only be used if the association on the join model is a
-
# +belongs_to+. In other situations you are expected to perform operations directly on
-
# either the associated records or the <tt>:through</tt> association.
-
#
-
# With a regular +has_many+ there is no distinction between the "associated records"
-
# and the "link", so there is only one choice for what gets deleted.
-
#
-
# With +has_and_belongs_to_many+ and +has_many+ <tt>:through</tt>, if you want to delete the
-
# associated records themselves, you can always do something along the lines of
-
# <tt>person.tasks.each(&:destroy)</tt>.
-
#
-
# == Type safety with <tt>ActiveRecord::AssociationTypeMismatch</tt>
-
#
-
# If you attempt to assign an object to an association that doesn't match the inferred
-
# or specified <tt>:class_name</tt>, you'll get an <tt>ActiveRecord::AssociationTypeMismatch</tt>.
-
#
-
# == Options
-
#
-
# All of the association macros can be specialized through options. This makes cases
-
# more complex than the simple and guessable ones possible.
-
1
module ClassMethods
-
# Specifies a one-to-many association. The following methods for retrieval and query of
-
# collections of associated objects will be added:
-
#
-
# +collection+ is a placeholder for the symbol passed as the +name+ argument, so
-
# <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.
-
#
-
# [collection(force_reload = false)]
-
# Returns an array of all the associated objects.
-
# An empty array is returned if none are found.
-
# [collection<<(object, ...)]
-
# Adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
-
# Note that this operation instantly fires update SQL without waiting for the save or update call on the
-
# parent object, unless the parent object is a new record.
-
# [collection.delete(object, ...)]
-
# Removes one or more objects from the collection by setting their foreign keys to +NULL+.
-
# Objects will be in addition destroyed if they're associated with <tt>dependent: :destroy</tt>,
-
# and deleted if they're associated with <tt>dependent: :delete_all</tt>.
-
#
-
# If the <tt>:through</tt> option is used, then the join records are deleted (rather than
-
# nullified) by default, but you can specify <tt>dependent: :destroy</tt> or
-
# <tt>dependent: :nullify</tt> to override this.
-
# [collection.destroy(object, ...)]
-
# Removes one or more objects from the collection by running <tt>destroy</tt> on
-
# each record, regardless of any dependent option, ensuring callbacks are run.
-
#
-
# If the <tt>:through</tt> option is used, then the join records are destroyed
-
# instead, not the objects themselves.
-
# [collection=objects]
-
# Replaces the collections content by deleting and adding objects as appropriate. If the <tt>:through</tt>
-
# option is true callbacks in the join models are triggered except destroy callbacks, since deletion is
-
# direct.
-
# [collection_singular_ids]
-
# Returns an array of the associated objects' ids
-
# [collection_singular_ids=ids]
-
# Replace the collection with the objects identified by the primary keys in +ids+. This
-
# method loads the models and calls <tt>collection=</tt>. See above.
-
# [collection.clear]
-
# Removes every object from the collection. This destroys the associated objects if they
-
# are associated with <tt>dependent: :destroy</tt>, deletes them directly from the
-
# database if <tt>dependent: :delete_all</tt>, otherwise sets their foreign keys to +NULL+.
-
# If the <tt>:through</tt> option is true no destroy callbacks are invoked on the join models.
-
# Join models are directly deleted.
-
# [collection.empty?]
-
# Returns +true+ if there are no associated objects.
-
# [collection.size]
-
# Returns the number of associated objects.
-
# [collection.find(...)]
-
# Finds an associated object according to the same rules as <tt>ActiveRecord::Base.find</tt>.
-
# [collection.exists?(...)]
-
# Checks whether an associated object with the given conditions exists.
-
# Uses the same rules as <tt>ActiveRecord::Base.exists?</tt>.
-
# [collection.build(attributes = {}, ...)]
-
# Returns one or more new objects of the collection type that have been instantiated
-
# with +attributes+ and linked to this object through a foreign key, but have not yet
-
# been saved.
-
# [collection.create(attributes = {})]
-
# Returns a new object of the collection type that has been instantiated
-
# with +attributes+, linked to this object through a foreign key, and that has already
-
# been saved (if it passed the validation). *Note*: This only works if the base model
-
# already exists in the DB, not if it is a new (unsaved) record!
-
# [collection.create!(attributes = {})]
-
# Does the same as <tt>collection.create</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt>
-
# if the record is invalid.
-
#
-
# === Example
-
#
-
# A <tt>Firm</tt> class declares <tt>has_many :clients</tt>, which will add:
-
# * <tt>Firm#clients</tt> (similar to <tt>Client.where(firm_id: id)</tt>)
-
# * <tt>Firm#clients<<</tt>
-
# * <tt>Firm#clients.delete</tt>
-
# * <tt>Firm#clients.destroy</tt>
-
# * <tt>Firm#clients=</tt>
-
# * <tt>Firm#client_ids</tt>
-
# * <tt>Firm#client_ids=</tt>
-
# * <tt>Firm#clients.clear</tt>
-
# * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>)
-
# * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>)
-
# * <tt>Firm#clients.find</tt> (similar to <tt>Client.where(firm_id: id).find(id)</tt>)
-
# * <tt>Firm#clients.exists?(name: 'ACME')</tt> (similar to <tt>Client.exists?(name: 'ACME', firm_id: firm.id)</tt>)
-
# * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>)
-
# * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>)
-
# * <tt>Firm#clients.create!</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save!</tt>)
-
# The declaration can also include an +options+ hash to specialize the behavior of the association.
-
#
-
# === Scopes
-
#
-
# You can pass a second argument +scope+ as a callable (i.e. proc or
-
# lambda) to retrieve a specific set of records or customize the generated
-
# query when you access the associated collection.
-
#
-
# Scope examples:
-
# has_many :comments, -> { where(author_id: 1) }
-
# has_many :employees, -> { joins(:address) }
-
# has_many :posts, ->(post) { where("max_post_length > ?", post.length) }
-
#
-
# === Extensions
-
#
-
# The +extension+ argument allows you to pass a block into a has_many
-
# association. This is useful for adding new finders, creators and other
-
# factory-type methods to be used as part of the association.
-
#
-
# Extension examples:
-
# has_many :employees do
-
# def find_or_create_by_name(name)
-
# first_name, last_name = name.split(" ", 2)
-
# find_or_create_by(first_name: first_name, last_name: last_name)
-
# end
-
# end
-
#
-
# === Options
-
# [:class_name]
-
# Specify the class name of the association. Use it only if that name can't be inferred
-
# from the association name. So <tt>has_many :products</tt> will by default be linked
-
# to the Product class, but if the real class name is SpecialProduct, you'll have to
-
# specify it with this option.
-
# [:foreign_key]
-
# Specify the foreign key used for the association. By default this is guessed to be the name
-
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_many+
-
# association will use "person_id" as the default <tt>:foreign_key</tt>.
-
# [:foreign_type]
-
# Specify the column used to store the associated object's type, if this is a polymorphic
-
# association. By default this is guessed to be the name of the polymorphic association
-
# specified on "as" option with a "_type" suffix. So a class that defines a
-
# <tt>has_many :tags, as: :taggable</tt> association will use "taggable_type" as the
-
# default <tt>:foreign_type</tt>.
-
# [:primary_key]
-
# Specify the name of the column to use as the primary key for the association. By default this is +id+.
-
# [:dependent]
-
# Controls what happens to the associated objects when
-
# their owner is destroyed. Note that these are implemented as
-
# callbacks, and Rails executes callbacks in order. Therefore, other
-
# similar callbacks may affect the <tt>:dependent</tt> behavior, and the
-
# <tt>:dependent</tt> behavior may affect other callbacks.
-
#
-
# * <tt>:destroy</tt> causes all the associated objects to also be destroyed.
-
# * <tt>:delete_all</tt> causes all the associated objects to be deleted directly from the database (so callbacks will not be executed).
-
# * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Callbacks are not executed.
-
# * <tt>:restrict_with_exception</tt> causes an exception to be raised if there are any associated records.
-
# * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there are any associated objects.
-
#
-
# If using with the <tt>:through</tt> option, the association on the join model must be
-
# a +belongs_to+, and the records which get deleted are the join records, rather than
-
# the associated records.
-
# [:counter_cache]
-
# This option can be used to configure a custom named <tt>:counter_cache.</tt> You only need this option,
-
# when you customized the name of your <tt>:counter_cache</tt> on the <tt>belongs_to</tt> association.
-
# [:as]
-
# Specifies a polymorphic interface (See <tt>belongs_to</tt>).
-
# [:through]
-
# Specifies an association through which to perform the query. This can be any other type
-
# of association, including other <tt>:through</tt> associations. Options for <tt>:class_name</tt>,
-
# <tt>:primary_key</tt> and <tt>:foreign_key</tt> are ignored, as the association uses the
-
# source reflection.
-
#
-
# If the association on the join model is a +belongs_to+, the collection can be modified
-
# and the records on the <tt>:through</tt> model will be automatically created and removed
-
# as appropriate. Otherwise, the collection is read-only, so you should manipulate the
-
# <tt>:through</tt> association directly.
-
#
-
# If you are going to modify the association (rather than just read from it), then it is
-
# a good idea to set the <tt>:inverse_of</tt> option on the source association on the
-
# join model. This allows associated records to be built which will automatically create
-
# the appropriate join model records when they are saved. (See the 'Association Join Models'
-
# section above.)
-
# [:source]
-
# Specifies the source association name used by <tt>has_many :through</tt> queries.
-
# Only use it if the name cannot be inferred from the association.
-
# <tt>has_many :subscribers, through: :subscriptions</tt> will look for either <tt>:subscribers</tt> or
-
# <tt>:subscriber</tt> on Subscription, unless a <tt>:source</tt> is given.
-
# [:source_type]
-
# Specifies type of the source association used by <tt>has_many :through</tt> queries where the source
-
# association is a polymorphic +belongs_to+.
-
# [:validate]
-
# If +false+, don't validate the associated objects when saving the parent object. true by default.
-
# [:autosave]
-
# If true, always save the associated objects or destroy them if marked for destruction,
-
# when saving the parent object. If false, never save or destroy the associated objects.
-
# By default, only save associated objects that are new records. This option is implemented as a
-
# +before_save+ callback. Because callbacks are run in the order they are defined, associated objects
-
# may need to be explicitly saved in any user-defined +before_save+ callbacks.
-
#
-
# Note that <tt>accepts_nested_attributes_for</tt> sets <tt>:autosave</tt> to <tt>true</tt>.
-
# [:inverse_of]
-
# Specifies the name of the <tt>belongs_to</tt> association on the associated object
-
# that is the inverse of this <tt>has_many</tt> association. Does not work in combination
-
# with <tt>:through</tt> or <tt>:as</tt> options.
-
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
-
#
-
# Option examples:
-
# has_many :comments, -> { order "posted_on" }
-
# has_many :comments, -> { includes :author }
-
# has_many :people, -> { where(deleted: false).order("name") }, class_name: "Person"
-
# has_many :tracks, -> { order "position" }, dependent: :destroy
-
# has_many :comments, dependent: :nullify
-
# has_many :tags, as: :taggable
-
# has_many :reports, -> { readonly }
-
# has_many :subscribers, through: :subscriptions, source: :user
-
1
def has_many(name, scope = nil, options = {}, &extension)
-
1
reflection = Builder::HasMany.build(self, name, scope, options, &extension)
-
1
Reflection.add_reflection self, name, reflection
-
end
-
-
# Specifies a one-to-one association with another class. This method should only be used
-
# if the other class contains the foreign key. If the current class contains the foreign key,
-
# then you should use +belongs_to+ instead. See also ActiveRecord::Associations::ClassMethods's overview
-
# on when to use +has_one+ and when to use +belongs_to+.
-
#
-
# The following methods for retrieval and query of a single associated object will be added:
-
#
-
# +association+ is a placeholder for the symbol passed as the +name+ argument, so
-
# <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.
-
#
-
# [association(force_reload = false)]
-
# Returns the associated object. +nil+ is returned if none is found.
-
# [association=(associate)]
-
# Assigns the associate object, extracts the primary key, sets it as the foreign key,
-
# and saves the associate object. To avoid database inconsistencies, permanently deletes an existing
-
# associated object when assigning a new one, even if the new one isn't saved to database.
-
# [build_association(attributes = {})]
-
# Returns a new object of the associated type that has been instantiated
-
# with +attributes+ and linked to this object through a foreign key, but has not
-
# yet been saved.
-
# [create_association(attributes = {})]
-
# Returns a new object of the associated type that has been instantiated
-
# with +attributes+, linked to this object through a foreign key, and that
-
# has already been saved (if it passed the validation).
-
# [create_association!(attributes = {})]
-
# Does the same as <tt>create_association</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt>
-
# if the record is invalid.
-
#
-
# === Example
-
#
-
# An Account class declares <tt>has_one :beneficiary</tt>, which will add:
-
# * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.where(account_id: id).first</tt>)
-
# * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>)
-
# * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>)
-
# * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>)
-
# * <tt>Account#create_beneficiary!</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save!; b</tt>)
-
#
-
# === Scopes
-
#
-
# You can pass a second argument +scope+ as a callable (i.e. proc or
-
# lambda) to retrieve a specific record or customize the generated query
-
# when you access the associated object.
-
#
-
# Scope examples:
-
# has_one :author, -> { where(comment_id: 1) }
-
# has_one :employer, -> { joins(:company) }
-
# has_one :dob, ->(dob) { where("Date.new(2000, 01, 01) > ?", dob) }
-
#
-
# === Options
-
#
-
# The declaration can also include an +options+ hash to specialize the behavior of the association.
-
#
-
# Options are:
-
# [:class_name]
-
# Specify the class name of the association. Use it only if that name can't be inferred
-
# from the association name. So <tt>has_one :manager</tt> will by default be linked to the Manager class, but
-
# if the real class name is Person, you'll have to specify it with this option.
-
# [:dependent]
-
# Controls what happens to the associated object when
-
# its owner is destroyed:
-
#
-
# * <tt>:destroy</tt> causes the associated object to also be destroyed
-
# * <tt>:delete</tt> causes the associated object to be deleted directly from the database (so callbacks will not execute)
-
# * <tt>:nullify</tt> causes the foreign key to be set to +NULL+. Callbacks are not executed.
-
# * <tt>:restrict_with_exception</tt> causes an exception to be raised if there is an associated record
-
# * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there is an associated object
-
# [:foreign_key]
-
# Specify the foreign key used for the association. By default this is guessed to be the name
-
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association
-
# will use "person_id" as the default <tt>:foreign_key</tt>.
-
# [:foreign_type]
-
# Specify the column used to store the associated object's type, if this is a polymorphic
-
# association. By default this is guessed to be the name of the polymorphic association
-
# specified on "as" option with a "_type" suffix. So a class that defines a
-
# <tt>has_one :tag, as: :taggable</tt> association will use "taggable_type" as the
-
# default <tt>:foreign_type</tt>.
-
# [:primary_key]
-
# Specify the method that returns the primary key used for the association. By default this is +id+.
-
# [:as]
-
# Specifies a polymorphic interface (See <tt>belongs_to</tt>).
-
# [:through]
-
# Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt>,
-
# <tt>:primary_key</tt>, and <tt>:foreign_key</tt> are ignored, as the association uses the
-
# source reflection. You can only use a <tt>:through</tt> query through a <tt>has_one</tt>
-
# or <tt>belongs_to</tt> association on the join model.
-
# [:source]
-
# Specifies the source association name used by <tt>has_one :through</tt> queries.
-
# Only use it if the name cannot be inferred from the association.
-
# <tt>has_one :favorite, through: :favorites</tt> will look for a
-
# <tt>:favorite</tt> on Favorite, unless a <tt>:source</tt> is given.
-
# [:source_type]
-
# Specifies type of the source association used by <tt>has_one :through</tt> queries where the source
-
# association is a polymorphic +belongs_to+.
-
# [:validate]
-
# If +false+, don't validate the associated object when saving the parent object. +false+ by default.
-
# [:autosave]
-
# If true, always save the associated object or destroy it if marked for destruction,
-
# when saving the parent object. If false, never save or destroy the associated object.
-
# By default, only save the associated object if it's a new record.
-
#
-
# Note that <tt>accepts_nested_attributes_for</tt> sets <tt>:autosave</tt> to <tt>true</tt>.
-
# [:inverse_of]
-
# Specifies the name of the <tt>belongs_to</tt> association on the associated object
-
# that is the inverse of this <tt>has_one</tt> association. Does not work in combination
-
# with <tt>:through</tt> or <tt>:as</tt> options.
-
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
-
# [:required]
-
# When set to +true+, the association will also have its presence validated.
-
# This will validate the association itself, not the id. You can use
-
# +:inverse_of+ to avoid an extra query during validation.
-
#
-
# Option examples:
-
# has_one :credit_card, dependent: :destroy # destroys the associated credit card
-
# has_one :credit_card, dependent: :nullify # updates the associated records foreign
-
# # key value to NULL rather than destroying it
-
# has_one :last_comment, -> { order 'posted_on' }, class_name: "Comment"
-
# has_one :project_manager, -> { where role: 'project_manager' }, class_name: "Person"
-
# has_one :attachment, as: :attachable
-
# has_one :boss, -> { readonly }
-
# has_one :club, through: :membership
-
# has_one :primary_address, -> { where primary: true }, through: :addressables, source: :addressable
-
# has_one :credit_card, required: true
-
1
def has_one(name, scope = nil, options = {})
-
reflection = Builder::HasOne.build(self, name, scope, options)
-
Reflection.add_reflection self, name, reflection
-
end
-
-
# Specifies a one-to-one association with another class. This method should only be used
-
# if this class contains the foreign key. If the other class contains the foreign key,
-
# then you should use +has_one+ instead. See also ActiveRecord::Associations::ClassMethods's overview
-
# on when to use +has_one+ and when to use +belongs_to+.
-
#
-
# Methods will be added for retrieval and query for a single associated object, for which
-
# this object holds an id:
-
#
-
# +association+ is a placeholder for the symbol passed as the +name+ argument, so
-
# <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.
-
#
-
# [association(force_reload = false)]
-
# Returns the associated object. +nil+ is returned if none is found.
-
# [association=(associate)]
-
# Assigns the associate object, extracts the primary key, and sets it as the foreign key.
-
# [build_association(attributes = {})]
-
# Returns a new object of the associated type that has been instantiated
-
# with +attributes+ and linked to this object through a foreign key, but has not yet been saved.
-
# [create_association(attributes = {})]
-
# Returns a new object of the associated type that has been instantiated
-
# with +attributes+, linked to this object through a foreign key, and that
-
# has already been saved (if it passed the validation).
-
# [create_association!(attributes = {})]
-
# Does the same as <tt>create_association</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt>
-
# if the record is invalid.
-
#
-
# === Example
-
#
-
# A Post class declares <tt>belongs_to :author</tt>, which will add:
-
# * <tt>Post#author</tt> (similar to <tt>Author.find(author_id)</tt>)
-
# * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>)
-
# * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>)
-
# * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>)
-
# * <tt>Post#create_author!</tt> (similar to <tt>post.author = Author.new; post.author.save!; post.author</tt>)
-
# The declaration can also include an +options+ hash to specialize the behavior of the association.
-
#
-
# === Scopes
-
#
-
# You can pass a second argument +scope+ as a callable (i.e. proc or
-
# lambda) to retrieve a specific record or customize the generated query
-
# when you access the associated object.
-
#
-
# Scope examples:
-
# belongs_to :user, -> { where(id: 2) }
-
# belongs_to :user, -> { joins(:friends) }
-
# belongs_to :level, ->(level) { where("game_level > ?", level.current) }
-
#
-
# === Options
-
#
-
# [:class_name]
-
# Specify the class name of the association. Use it only if that name can't be inferred
-
# from the association name. So <tt>belongs_to :author</tt> will by default be linked to the Author class, but
-
# if the real class name is Person, you'll have to specify it with this option.
-
# [:foreign_key]
-
# Specify the foreign key used for the association. By default this is guessed to be the name
-
# of the association with an "_id" suffix. So a class that defines a <tt>belongs_to :person</tt>
-
# association will use "person_id" as the default <tt>:foreign_key</tt>. Similarly,
-
# <tt>belongs_to :favorite_person, class_name: "Person"</tt> will use a foreign key
-
# of "favorite_person_id".
-
# [:foreign_type]
-
# Specify the column used to store the associated object's type, if this is a polymorphic
-
# association. By default this is guessed to be the name of the association with a "_type"
-
# suffix. So a class that defines a <tt>belongs_to :taggable, polymorphic: true</tt>
-
# association will use "taggable_type" as the default <tt>:foreign_type</tt>.
-
# [:primary_key]
-
# Specify the method that returns the primary key of associated object used for the association.
-
# By default this is id.
-
# [:dependent]
-
# If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
-
# <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method.
-
# This option should not be specified when <tt>belongs_to</tt> is used in conjunction with
-
# a <tt>has_many</tt> relationship on another class because of the potential to leave
-
# orphaned records behind.
-
# [:counter_cache]
-
# Caches the number of belonging objects on the associate class through the use of +increment_counter+
-
# and +decrement_counter+. The counter cache is incremented when an object of this
-
# class is created and decremented when it's destroyed. This requires that a column
-
# named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging Comment class)
-
# is used on the associate class (such as a Post class) - that is the migration for
-
# <tt>#{table_name}_count</tt> is created on the associate class (such that <tt>Post.comments_count</tt> will
-
# return the count cached, see note below). You can also specify a custom counter
-
# cache column by providing a column name instead of a +true+/+false+ value to this
-
# option (e.g., <tt>counter_cache: :my_custom_counter</tt>.)
-
# Note: Specifying a counter cache will add it to that model's list of readonly attributes
-
# using +attr_readonly+.
-
# [:polymorphic]
-
# Specify this association is a polymorphic association by passing +true+.
-
# Note: If you've enabled the counter cache, then you may want to add the counter cache attribute
-
# to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>).
-
# [:validate]
-
# If +false+, don't validate the associated objects when saving the parent object. +false+ by default.
-
# [:autosave]
-
# If true, always save the associated object or destroy it if marked for destruction, when
-
# saving the parent object.
-
# If false, never save or destroy the associated object.
-
# By default, only save the associated object if it's a new record.
-
#
-
# Note that <tt>accepts_nested_attributes_for</tt> sets <tt>:autosave</tt> to <tt>true</tt>.
-
# [:touch]
-
# If true, the associated object will be touched (the updated_at/on attributes set to current time)
-
# when this record is either saved or destroyed. If you specify a symbol, that attribute
-
# will be updated with the current time in addition to the updated_at/on attribute.
-
# [:inverse_of]
-
# Specifies the name of the <tt>has_one</tt> or <tt>has_many</tt> association on the associated
-
# object that is the inverse of this <tt>belongs_to</tt> association. Does not work in
-
# combination with the <tt>:polymorphic</tt> options.
-
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
-
# [:required]
-
# When set to +true+, the association will also have its presence validated.
-
# This will validate the association itself, not the id. You can use
-
# +:inverse_of+ to avoid an extra query during validation.
-
#
-
# Option examples:
-
# belongs_to :firm, foreign_key: "client_of"
-
# belongs_to :person, primary_key: "name", foreign_key: "person_name"
-
# belongs_to :author, class_name: "Person", foreign_key: "author_id"
-
# belongs_to :valid_coupon, ->(o) { where "discounts > ?", o.payments_count },
-
# class_name: "Coupon", foreign_key: "coupon_id"
-
# belongs_to :attachable, polymorphic: true
-
# belongs_to :project, -> { readonly }
-
# belongs_to :post, counter_cache: true
-
# belongs_to :company, touch: true
-
# belongs_to :company, touch: :employees_last_updated_at
-
# belongs_to :company, required: true
-
1
def belongs_to(name, scope = nil, options = {})
-
1
reflection = Builder::BelongsTo.build(self, name, scope, options)
-
1
Reflection.add_reflection self, name, reflection
-
end
-
-
# Specifies a many-to-many relationship with another class. This associates two classes via an
-
# intermediate join table. Unless the join table is explicitly specified as an option, it is
-
# guessed using the lexical order of the class names. So a join between Developer and Project
-
# will give the default join table name of "developers_projects" because "D" precedes "P" alphabetically.
-
# Note that this precedence is calculated using the <tt><</tt> operator for String. This
-
# means that if the strings are of different lengths, and the strings are equal when compared
-
# up to the shortest length, then the longer string is considered of higher
-
# lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers"
-
# to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes",
-
# but it in fact generates a join table name of "paper_boxes_papers". Be aware of this caveat, and use the
-
# custom <tt>:join_table</tt> option if you need to.
-
# If your tables share a common prefix, it will only appear once at the beginning. For example,
-
# the tables "catalog_categories" and "catalog_products" generate a join table name of "catalog_categories_products".
-
#
-
# The join table should not have a primary key or a model associated with it. You must manually generate the
-
# join table with a migration such as this:
-
#
-
# class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration
-
# def change
-
# create_table :developers_projects, id: false do |t|
-
# t.integer :developer_id
-
# t.integer :project_id
-
# end
-
# end
-
# end
-
#
-
# It's also a good idea to add indexes to each of those columns to speed up the joins process.
-
# However, in MySQL it is advised to add a compound index for both of the columns as MySQL only
-
# uses one index per table during the lookup.
-
#
-
# Adds the following methods for retrieval and query:
-
#
-
# +collection+ is a placeholder for the symbol passed as the +name+ argument, so
-
# <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>.
-
#
-
# [collection(force_reload = false)]
-
# Returns an array of all the associated objects.
-
# An empty array is returned if none are found.
-
# [collection<<(object, ...)]
-
# Adds one or more objects to the collection by creating associations in the join table
-
# (<tt>collection.push</tt> and <tt>collection.concat</tt> are aliases to this method).
-
# Note that this operation instantly fires update SQL without waiting for the save or update call on the
-
# parent object, unless the parent object is a new record.
-
# [collection.delete(object, ...)]
-
# Removes one or more objects from the collection by removing their associations from the join table.
-
# This does not destroy the objects.
-
# [collection.destroy(object, ...)]
-
# Removes one or more objects from the collection by running destroy on each association in the join table, overriding any dependent option.
-
# This does not destroy the objects.
-
# [collection=objects]
-
# Replaces the collection's content by deleting and adding objects as appropriate.
-
# [collection_singular_ids]
-
# Returns an array of the associated objects' ids.
-
# [collection_singular_ids=ids]
-
# Replace the collection by the objects identified by the primary keys in +ids+.
-
# [collection.clear]
-
# Removes every object from the collection. This does not destroy the objects.
-
# [collection.empty?]
-
# Returns +true+ if there are no associated objects.
-
# [collection.size]
-
# Returns the number of associated objects.
-
# [collection.find(id)]
-
# Finds an associated object responding to the +id+ and that
-
# meets the condition that it has to be associated with this object.
-
# Uses the same rules as <tt>ActiveRecord::Base.find</tt>.
-
# [collection.exists?(...)]
-
# Checks whether an associated object with the given conditions exists.
-
# Uses the same rules as <tt>ActiveRecord::Base.exists?</tt>.
-
# [collection.build(attributes = {})]
-
# Returns a new object of the collection type that has been instantiated
-
# with +attributes+ and linked to this object through the join table, but has not yet been saved.
-
# [collection.create(attributes = {})]
-
# Returns a new object of the collection type that has been instantiated
-
# with +attributes+, linked to this object through the join table, and that has already been
-
# saved (if it passed the validation).
-
#
-
# === Example
-
#
-
# A Developer class declares <tt>has_and_belongs_to_many :projects</tt>, which will add:
-
# * <tt>Developer#projects</tt>
-
# * <tt>Developer#projects<<</tt>
-
# * <tt>Developer#projects.delete</tt>
-
# * <tt>Developer#projects.destroy</tt>
-
# * <tt>Developer#projects=</tt>
-
# * <tt>Developer#project_ids</tt>
-
# * <tt>Developer#project_ids=</tt>
-
# * <tt>Developer#projects.clear</tt>
-
# * <tt>Developer#projects.empty?</tt>
-
# * <tt>Developer#projects.size</tt>
-
# * <tt>Developer#projects.find(id)</tt>
-
# * <tt>Developer#projects.exists?(...)</tt>
-
# * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("developer_id" => id)</tt>)
-
# * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("developer_id" => id); c.save; c</tt>)
-
# The declaration may include an +options+ hash to specialize the behavior of the association.
-
#
-
# === Scopes
-
#
-
# You can pass a second argument +scope+ as a callable (i.e. proc or
-
# lambda) to retrieve a specific set of records or customize the generated
-
# query when you access the associated collection.
-
#
-
# Scope examples:
-
# has_and_belongs_to_many :projects, -> { includes :milestones, :manager }
-
# has_and_belongs_to_many :categories, ->(category) {
-
# where("default_category = ?", category.name)
-
# }
-
#
-
# === Extensions
-
#
-
# The +extension+ argument allows you to pass a block into a
-
# has_and_belongs_to_many association. This is useful for adding new
-
# finders, creators and other factory-type methods to be used as part of
-
# the association.
-
#
-
# Extension examples:
-
# has_and_belongs_to_many :contractors do
-
# def find_or_create_by_name(name)
-
# first_name, last_name = name.split(" ", 2)
-
# find_or_create_by(first_name: first_name, last_name: last_name)
-
# end
-
# end
-
#
-
# === Options
-
#
-
# [:class_name]
-
# Specify the class name of the association. Use it only if that name can't be inferred
-
# from the association name. So <tt>has_and_belongs_to_many :projects</tt> will by default be linked to the
-
# Project class, but if the real class name is SuperProject, you'll have to specify it with this option.
-
# [:join_table]
-
# Specify the name of the join table if the default based on lexical order isn't what you want.
-
# <b>WARNING:</b> If you're overwriting the table name of either class, the +table_name+ method
-
# MUST be declared underneath any +has_and_belongs_to_many+ declaration in order to work.
-
# [:foreign_key]
-
# Specify the foreign key used for the association. By default this is guessed to be the name
-
# of this class in lower-case and "_id" suffixed. So a Person class that makes
-
# a +has_and_belongs_to_many+ association to Project will use "person_id" as the
-
# default <tt>:foreign_key</tt>.
-
# [:association_foreign_key]
-
# Specify the foreign key used for the association on the receiving side of the association.
-
# By default this is guessed to be the name of the associated class in lower-case and "_id" suffixed.
-
# So if a Person class makes a +has_and_belongs_to_many+ association to Project,
-
# the association will use "project_id" as the default <tt>:association_foreign_key</tt>.
-
# [:readonly]
-
# If true, all the associated objects are readonly through the association.
-
# [:validate]
-
# If +false+, don't validate the associated objects when saving the parent object. +true+ by default.
-
# [:autosave]
-
# If true, always save the associated objects or destroy them if marked for destruction, when
-
# saving the parent object.
-
# If false, never save or destroy the associated objects.
-
# By default, only save associated objects that are new records.
-
#
-
# Note that <tt>accepts_nested_attributes_for</tt> sets <tt>:autosave</tt> to <tt>true</tt>.
-
#
-
# Option examples:
-
# has_and_belongs_to_many :projects
-
# has_and_belongs_to_many :projects, -> { includes :milestones, :manager }
-
# has_and_belongs_to_many :nations, class_name: "Country"
-
# has_and_belongs_to_many :categories, join_table: "prods_cats"
-
# has_and_belongs_to_many :categories, -> { readonly }
-
1
def has_and_belongs_to_many(name, scope = nil, options = {}, &extension)
-
if scope.is_a?(Hash)
-
options = scope
-
scope = nil
-
end
-
-
habtm_reflection = ActiveRecord::Reflection::HasAndBelongsToManyReflection.new(name, scope, options, self)
-
-
builder = Builder::HasAndBelongsToMany.new name, self, options
-
-
join_model = builder.through_model
-
-
# FIXME: we should move this to the internal constants. Also people
-
# should never directly access this constant so I'm not happy about
-
# setting it.
-
const_set join_model.name, join_model
-
-
middle_reflection = builder.middle_reflection join_model
-
-
Builder::HasMany.define_callbacks self, middle_reflection
-
Reflection.add_reflection self, middle_reflection.name, middle_reflection
-
middle_reflection.parent_reflection = [name.to_s, habtm_reflection]
-
-
include Module.new {
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
-
def destroy_associations
-
association(:#{middle_reflection.name}).delete_all(:delete_all)
-
association(:#{name}).reset
-
super
-
end
-
RUBY
-
}
-
-
hm_options = {}
-
hm_options[:through] = middle_reflection.name
-
hm_options[:source] = join_model.right_reflection.name
-
-
[:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name, :extend].each do |k|
-
hm_options[k] = options[k] if options.key? k
-
end
-
-
has_many name, scope, hm_options, &extension
-
self._reflections[name.to_s].parent_reflection = [name.to_s, habtm_reflection]
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/string/conversions'
-
-
1
module ActiveRecord
-
1
module Associations
-
# Keeps track of table aliases for ActiveRecord::Associations::ClassMethods::JoinDependency and
-
# ActiveRecord::Associations::ThroughAssociationScope
-
1
class AliasTracker # :nodoc:
-
1
attr_reader :aliases, :connection
-
-
1
def self.empty(connection)
-
72
new connection, Hash.new(0)
-
end
-
-
1
def self.create(connection, table_joins)
-
72
if table_joins.empty?
-
72
empty connection
-
else
-
aliases = Hash.new { |h,k|
-
h[k] = initial_count_for(connection, k, table_joins)
-
}
-
new connection, aliases
-
end
-
end
-
-
1
def self.initial_count_for(connection, name, table_joins)
-
# quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
-
quoted_name = connection.quote_table_name(name).downcase
-
-
counts = table_joins.map do |join|
-
if join.is_a?(Arel::Nodes::StringJoin)
-
# Table names + table aliases
-
join.left.downcase.scan(
-
/join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
-
).size
-
elsif join.respond_to? :left
-
join.left.table_name == name ? 1 : 0
-
else
-
# this branch is reached by two tests:
-
#
-
# activerecord/test/cases/associations/cascaded_eager_loading_test.rb:37
-
# with :posts
-
#
-
# activerecord/test/cases/associations/eager_test.rb:1133
-
# with :comments
-
#
-
0
-
end
-
end
-
-
counts.sum
-
end
-
-
# table_joins is an array of arel joins which might conflict with the aliases we assign here
-
1
def initialize(connection, aliases)
-
72
@aliases = aliases
-
72
@connection = connection
-
end
-
-
1
def aliased_table_for(table_name, aliased_name)
-
72
if aliases[table_name].zero?
-
# If it's zero, we can have our table_name
-
72
aliases[table_name] = 1
-
72
Arel::Table.new(table_name)
-
else
-
# Otherwise, we need to use an alias
-
aliased_name = connection.table_alias_for(aliased_name)
-
-
# Update the count
-
aliases[aliased_name] += 1
-
-
table_alias = if aliases[aliased_name] > 1
-
"#{truncate(aliased_name)}_#{aliases[aliased_name]}"
-
else
-
aliased_name
-
end
-
Arel::Table.new(table_name).alias(table_alias)
-
end
-
end
-
-
1
private
-
-
1
def truncate(name)
-
name.slice(0, connection.table_alias_length - 2)
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/module/attribute_accessors'
-
-
# This is the parent Association class which defines the variables
-
# used by all associations.
-
#
-
# The hierarchy is defined as follows:
-
# Association
-
# - SingularAssociation
-
# - BelongsToAssociation
-
# - HasOneAssociation
-
# - CollectionAssociation
-
# - HasManyAssociation
-
-
1
module ActiveRecord::Associations::Builder
-
1
class Association #:nodoc:
-
1
class << self
-
1
attr_accessor :extensions
-
# TODO: This class accessor is needed to make activerecord-deprecated_finders work.
-
# We can move it to a constant in 5.0.
-
1
attr_accessor :valid_options
-
end
-
1
self.extensions = []
-
-
1
self.valid_options = [:class_name, :anonymous_class, :foreign_key, :validate]
-
-
1
attr_reader :name, :scope, :options
-
-
1
def self.build(model, name, scope, options, &block)
-
2
if model.dangerous_attribute_method?(name)
-
raise ArgumentError, "You tried to define an association named #{name} on the model #{model.name}, but " \
-
"this will conflict with a method #{name} already defined by Active Record. " \
-
"Please choose a different association name."
-
end
-
-
2
builder = create_builder model, name, scope, options, &block
-
2
reflection = builder.build(model)
-
2
define_accessors model, reflection
-
2
define_callbacks model, reflection
-
2
define_validations model, reflection
-
2
builder.define_extensions model
-
2
reflection
-
end
-
-
1
def self.create_builder(model, name, scope, options, &block)
-
2
raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
-
-
2
new(model, name, scope, options, &block)
-
end
-
-
1
def initialize(model, name, scope, options)
-
# TODO: Move this to create_builder as soon we drop support to activerecord-deprecated_finders.
-
2
if scope.is_a?(Hash)
-
1
options = scope
-
1
scope = nil
-
end
-
-
# TODO: Remove this model argument as soon we drop support to activerecord-deprecated_finders.
-
2
@name = name
-
2
@scope = scope
-
2
@options = options
-
-
2
validate_options
-
-
2
if scope && scope.arity == 0
-
@scope = proc { instance_exec(&scope) }
-
end
-
end
-
-
1
def build(model)
-
2
ActiveRecord::Reflection.create(macro, name, scope, options, model)
-
end
-
-
1
def macro
-
raise NotImplementedError
-
end
-
-
1
def valid_options
-
2
Association.valid_options + Association.extensions.flat_map(&:valid_options)
-
end
-
-
1
def validate_options
-
2
options.assert_valid_keys(valid_options)
-
end
-
-
1
def define_extensions(model)
-
end
-
-
1
def self.define_callbacks(model, reflection)
-
2
if dependent = reflection.options[:dependent]
-
1
check_dependent_options(dependent)
-
1
add_destroy_callbacks(model, reflection)
-
end
-
-
2
Association.extensions.each do |extension|
-
2
extension.build model, reflection
-
end
-
end
-
-
# Defines the setter and getter methods for the association
-
# class Post < ActiveRecord::Base
-
# has_many :comments
-
# end
-
#
-
# Post.first.comments and Post.first.comments= methods are defined by this method...
-
1
def self.define_accessors(model, reflection)
-
2
mixin = model.generated_association_methods
-
2
name = reflection.name
-
2
define_readers(mixin, name)
-
2
define_writers(mixin, name)
-
end
-
-
1
def self.define_readers(mixin, name)
-
2
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def #{name}(*args)
-
association(:#{name}).reader(*args)
-
end
-
CODE
-
end
-
-
1
def self.define_writers(mixin, name)
-
2
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def #{name}=(value)
-
association(:#{name}).writer(value)
-
end
-
CODE
-
end
-
-
1
def self.define_validations(model, reflection)
-
# noop
-
end
-
-
1
def self.valid_dependent_options
-
raise NotImplementedError
-
end
-
-
1
private
-
-
1
def self.check_dependent_options(dependent)
-
1
unless valid_dependent_options.include? dependent
-
raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{dependent}"
-
end
-
end
-
-
1
def self.add_destroy_callbacks(model, reflection)
-
1
name = reflection.name
-
1
model.before_destroy lambda { |o| o.association(name).handle_dependency }
-
end
-
end
-
end
-
1
module ActiveRecord::Associations::Builder
-
1
class BelongsTo < SingularAssociation #:nodoc:
-
1
def macro
-
1
:belongs_to
-
end
-
-
1
def valid_options
-
1
super + [:foreign_type, :polymorphic, :touch, :counter_cache]
-
end
-
-
1
def self.valid_dependent_options
-
[:destroy, :delete]
-
end
-
-
1
def self.define_callbacks(model, reflection)
-
1
super
-
1
add_counter_cache_callbacks(model, reflection) if reflection.options[:counter_cache]
-
1
add_touch_callbacks(model, reflection) if reflection.options[:touch]
-
end
-
-
1
def self.define_accessors(mixin, reflection)
-
1
super
-
1
add_counter_cache_methods mixin
-
end
-
-
1
private
-
-
1
def self.add_counter_cache_methods(mixin)
-
1
return if mixin.method_defined? :belongs_to_counter_cache_after_update
-
-
1
mixin.class_eval do
-
1
def belongs_to_counter_cache_after_update(reflection)
-
foreign_key = reflection.foreign_key
-
cache_column = reflection.counter_cache_column
-
-
if (@_after_create_counter_called ||= false)
-
@_after_create_counter_called = false
-
elsif attribute_changed?(foreign_key) && !new_record? && reflection.constructable?
-
model = reflection.klass
-
foreign_key_was = attribute_was foreign_key
-
foreign_key = attribute foreign_key
-
-
if foreign_key && model.respond_to?(:increment_counter)
-
model.increment_counter(cache_column, foreign_key)
-
end
-
if foreign_key_was && model.respond_to?(:decrement_counter)
-
model.decrement_counter(cache_column, foreign_key_was)
-
end
-
end
-
end
-
end
-
end
-
-
1
def self.add_counter_cache_callbacks(model, reflection)
-
cache_column = reflection.counter_cache_column
-
-
model.after_update lambda { |record|
-
record.belongs_to_counter_cache_after_update(reflection)
-
}
-
-
klass = reflection.class_name.safe_constantize
-
klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
-
end
-
-
1
def self.touch_record(o, foreign_key, name, touch) # :nodoc:
-
old_foreign_id = o.changed_attributes[foreign_key]
-
-
if old_foreign_id
-
association = o.association(name)
-
reflection = association.reflection
-
if reflection.polymorphic?
-
klass = o.public_send("#{reflection.foreign_type}_was").constantize
-
else
-
klass = association.klass
-
end
-
old_record = klass.find_by(klass.primary_key => old_foreign_id)
-
-
if old_record
-
if touch != true
-
old_record.touch touch
-
else
-
old_record.touch
-
end
-
end
-
end
-
-
record = o.send name
-
if record && record.persisted?
-
if touch != true
-
record.touch touch
-
else
-
record.touch
-
end
-
end
-
end
-
-
1
def self.add_touch_callbacks(model, reflection)
-
foreign_key = reflection.foreign_key
-
n = reflection.name
-
touch = reflection.options[:touch]
-
-
callback = lambda { |record|
-
BelongsTo.touch_record(record, foreign_key, n, touch)
-
}
-
-
model.after_save callback, if: :changed?
-
model.after_touch callback
-
model.after_destroy callback
-
end
-
-
1
def self.add_destroy_callbacks(model, reflection)
-
name = reflection.name
-
model.after_destroy lambda { |o| o.association(name).handle_dependency }
-
end
-
end
-
end
-
# This class is inherited by the has_many and has_many_and_belongs_to_many association classes
-
-
1
require 'active_record/associations'
-
-
1
module ActiveRecord::Associations::Builder
-
1
class CollectionAssociation < Association #:nodoc:
-
-
1
CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
-
-
1
def valid_options
-
super + [:table_name, :before_add,
-
1
:after_add, :before_remove, :after_remove, :extend]
-
end
-
-
1
attr_reader :block_extension
-
-
1
def initialize(model, name, scope, options)
-
1
super
-
1
@mod = nil
-
1
if block_given?
-
@mod = Module.new(&Proc.new)
-
@scope = wrap_scope @scope, @mod
-
end
-
end
-
-
1
def self.define_callbacks(model, reflection)
-
1
super
-
1
name = reflection.name
-
1
options = reflection.options
-
1
CALLBACKS.each { |callback_name|
-
4
define_callback(model, callback_name, name, options)
-
}
-
end
-
-
1
def define_extensions(model)
-
1
if @mod
-
extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
-
model.parent.const_set(extension_module_name, @mod)
-
end
-
end
-
-
1
def self.define_callback(model, callback_name, name, options)
-
4
full_callback_name = "#{callback_name}_for_#{name}"
-
-
# TODO : why do i need method_defined? I think its because of the inheritance chain
-
4
model.class_attribute full_callback_name unless model.method_defined?(full_callback_name)
-
4
callbacks = Array(options[callback_name.to_sym]).map do |callback|
-
case callback
-
when Symbol
-
->(method, owner, record) { owner.send(callback, record) }
-
when Proc
-
->(method, owner, record) { callback.call(owner, record) }
-
else
-
->(method, owner, record) { callback.send(method, owner, record) }
-
end
-
end
-
4
model.send "#{full_callback_name}=", callbacks
-
end
-
-
# Defines the setter and getter methods for the collection_singular_ids.
-
1
def self.define_readers(mixin, name)
-
1
super
-
-
1
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def #{name.to_s.singularize}_ids
-
association(:#{name}).ids_reader
-
end
-
CODE
-
end
-
-
1
def self.define_writers(mixin, name)
-
1
super
-
-
1
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def #{name.to_s.singularize}_ids=(ids)
-
association(:#{name}).ids_writer(ids)
-
end
-
CODE
-
end
-
-
1
private
-
-
1
def wrap_scope(scope, mod)
-
if scope
-
if scope.arity > 0
-
proc { |owner| instance_exec(owner, &scope).extending(mod) }
-
else
-
proc { instance_exec(&scope).extending(mod) }
-
end
-
else
-
proc { extending(mod) }
-
end
-
end
-
end
-
end
-
1
module ActiveRecord::Associations::Builder
-
1
class HasMany < CollectionAssociation #:nodoc:
-
1
def macro
-
1
:has_many
-
end
-
-
1
def valid_options
-
1
super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table, :foreign_type]
-
end
-
-
1
def self.valid_dependent_options
-
1
[:destroy, :delete_all, :nullify, :restrict_with_error, :restrict_with_exception]
-
end
-
end
-
end
-
# This class is inherited by the has_one and belongs_to association classes
-
-
1
module ActiveRecord::Associations::Builder
-
1
class SingularAssociation < Association #:nodoc:
-
1
def valid_options
-
1
super + [:dependent, :primary_key, :inverse_of, :required]
-
end
-
-
1
def self.define_accessors(model, reflection)
-
1
super
-
1
define_constructors(model.generated_association_methods, reflection.name) if reflection.constructable?
-
end
-
-
# Defines the (build|create)_association methods for belongs_to or has_one association
-
1
def self.define_constructors(mixin, name)
-
1
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def build_#{name}(*args, &block)
-
association(:#{name}).build(*args, &block)
-
end
-
-
def create_#{name}(*args, &block)
-
association(:#{name}).create(*args, &block)
-
end
-
-
def create_#{name}!(*args, &block)
-
association(:#{name}).create!(*args, &block)
-
end
-
CODE
-
end
-
-
1
def self.define_validations(model, reflection)
-
1
super
-
1
if reflection.options[:required]
-
model.validates_presence_of reflection.name
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Associations
-
# Association proxies in Active Record are middlemen between the object that
-
# holds the association, known as the <tt>@owner</tt>, and the actual associated
-
# object, known as the <tt>@target</tt>. The kind of association any proxy is
-
# about is available in <tt>@reflection</tt>. That's an instance of the class
-
# ActiveRecord::Reflection::AssociationReflection.
-
#
-
# For example, given
-
#
-
# class Blog < ActiveRecord::Base
-
# has_many :posts
-
# end
-
#
-
# blog = Blog.first
-
#
-
# the association proxy in <tt>blog.posts</tt> has the object in +blog+ as
-
# <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and
-
# the <tt>@reflection</tt> object represents a <tt>:has_many</tt> macro.
-
#
-
# This class delegates unknown methods to <tt>@target</tt> via
-
# <tt>method_missing</tt>.
-
#
-
# The <tt>@target</tt> object is not \loaded until needed. For example,
-
#
-
# blog.posts.count
-
#
-
# is computed directly through SQL and does not trigger by itself the
-
# instantiation of the actual post records.
-
1
class CollectionProxy < Relation
-
1
delegate(*(ActiveRecord::Calculations.public_instance_methods - [:count]), to: :scope)
-
1
delegate :find_nth, to: :scope
-
-
1
def initialize(klass, association) #:nodoc:
-
@association = association
-
super klass, klass.arel_table
-
merge! association.scope(nullify: false)
-
end
-
-
1
def target
-
@association.target
-
end
-
-
1
def load_target
-
@association.load_target
-
end
-
-
# Returns +true+ if the association has been loaded, otherwise +false+.
-
#
-
# person.pets.loaded? # => false
-
# person.pets
-
# person.pets.loaded? # => true
-
1
def loaded?
-
@association.loaded?
-
end
-
-
# Works in two ways.
-
#
-
# *First:* Specify a subset of fields to be selected from the result set.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.select(:name)
-
# # => [
-
# # #<Pet id: nil, name: "Fancy-Fancy">,
-
# # #<Pet id: nil, name: "Spook">,
-
# # #<Pet id: nil, name: "Choo-Choo">
-
# # ]
-
#
-
# person.pets.select(:id, :name )
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy">,
-
# # #<Pet id: 2, name: "Spook">,
-
# # #<Pet id: 3, name: "Choo-Choo">
-
# # ]
-
#
-
# Be careful because this also means you're initializing a model
-
# object with only the fields that you've selected. If you attempt
-
# to access a field except +id+ that is not in the initialized record you'll
-
# receive:
-
#
-
# person.pets.select(:name).first.person_id
-
# # => ActiveModel::MissingAttributeError: missing attribute: person_id
-
#
-
# *Second:* You can pass a block so it can be used just like Array#select.
-
# This builds an array of objects from the database for the scope,
-
# converting them into an array and iterating through them using
-
# Array#select.
-
#
-
# person.pets.select { |pet| pet.name =~ /oo/ }
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.select(:name) { |pet| pet.name =~ /oo/ }
-
# # => [
-
# # #<Pet id: 2, name: "Spook">,
-
# # #<Pet id: 3, name: "Choo-Choo">
-
# # ]
-
1
def select(*fields, &block)
-
@association.select(*fields, &block)
-
end
-
-
# Finds an object in the collection responding to the +id+. Uses the same
-
# rules as <tt>ActiveRecord::Base.find</tt>. Returns <tt>ActiveRecord::RecordNotFound</tt>
-
# error if the object cannot be found.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.find(1) # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
-
# person.pets.find(4) # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=4
-
#
-
# person.pets.find(2) { |pet| pet.name.downcase! }
-
# # => #<Pet id: 2, name: "fancy-fancy", person_id: 1>
-
#
-
# person.pets.find(2, 3)
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
1
def find(*args, &block)
-
@association.find(*args, &block)
-
end
-
-
# Returns the first record, or the first +n+ records, from the collection.
-
# If the collection is empty, the first form returns +nil+, and the second
-
# form returns an empty array.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.first # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
-
#
-
# person.pets.first(2)
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>
-
# # ]
-
#
-
# another_person_without.pets # => []
-
# another_person_without.pets.first # => nil
-
# another_person_without.pets.first(3) # => []
-
1
def first(*args)
-
@association.first(*args)
-
end
-
-
# Same as +first+ except returns only the second record.
-
1
def second(*args)
-
@association.second(*args)
-
end
-
-
# Same as +first+ except returns only the third record.
-
1
def third(*args)
-
@association.third(*args)
-
end
-
-
# Same as +first+ except returns only the fourth record.
-
1
def fourth(*args)
-
@association.fourth(*args)
-
end
-
-
# Same as +first+ except returns only the fifth record.
-
1
def fifth(*args)
-
@association.fifth(*args)
-
end
-
-
# Same as +first+ except returns only the forty second record.
-
# Also known as accessing "the reddit".
-
1
def forty_two(*args)
-
@association.forty_two(*args)
-
end
-
-
# Returns the last record, or the last +n+ records, from the collection.
-
# If the collection is empty, the first form returns +nil+, and the second
-
# form returns an empty array.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.last # => #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
#
-
# person.pets.last(2)
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# another_person_without.pets # => []
-
# another_person_without.pets.last # => nil
-
# another_person_without.pets.last(3) # => []
-
1
def last(*args)
-
@association.last(*args)
-
end
-
-
1
def take(n = nil)
-
@association.take(n)
-
end
-
-
# Returns a new object of the collection type that has been instantiated
-
# with +attributes+ and linked to this object, but have not yet been saved.
-
# You can pass an array of attributes hashes, this will return an array
-
# with the new objects.
-
#
-
# class Person
-
# has_many :pets
-
# end
-
#
-
# person.pets.build
-
# # => #<Pet id: nil, name: nil, person_id: 1>
-
#
-
# person.pets.build(name: 'Fancy-Fancy')
-
# # => #<Pet id: nil, name: "Fancy-Fancy", person_id: 1>
-
#
-
# person.pets.build([{name: 'Spook'}, {name: 'Choo-Choo'}, {name: 'Brain'}])
-
# # => [
-
# # #<Pet id: nil, name: "Spook", person_id: 1>,
-
# # #<Pet id: nil, name: "Choo-Choo", person_id: 1>,
-
# # #<Pet id: nil, name: "Brain", person_id: 1>
-
# # ]
-
#
-
# person.pets.size # => 5 # size of the collection
-
# person.pets.count # => 0 # count from database
-
1
def build(attributes = {}, &block)
-
@association.build(attributes, &block)
-
end
-
1
alias_method :new, :build
-
-
# Returns a new object of the collection type that has been instantiated with
-
# attributes, linked to this object and that has already been saved (if it
-
# passes the validations).
-
#
-
# class Person
-
# has_many :pets
-
# end
-
#
-
# person.pets.create(name: 'Fancy-Fancy')
-
# # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
-
#
-
# person.pets.create([{name: 'Spook'}, {name: 'Choo-Choo'}])
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.size # => 3
-
# person.pets.count # => 3
-
#
-
# person.pets.find(1, 2, 3)
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
1
def create(attributes = {}, &block)
-
@association.create(attributes, &block)
-
end
-
-
# Like +create+, except that if the record is invalid, raises an exception.
-
#
-
# class Person
-
# has_many :pets
-
# end
-
#
-
# class Pet
-
# validates :name, presence: true
-
# end
-
#
-
# person.pets.create!(name: nil)
-
# # => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
-
1
def create!(attributes = {}, &block)
-
@association.create!(attributes, &block)
-
end
-
-
# Add one or more records to the collection by setting their foreign keys
-
# to the association's primary key. Since << flattens its argument list and
-
# inserts each record, +push+ and +concat+ behave identically. Returns +self+
-
# so method calls may be chained.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.size # => 0
-
# person.pets.concat(Pet.new(name: 'Fancy-Fancy'))
-
# person.pets.concat(Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo'))
-
# person.pets.size # => 3
-
#
-
# person.id # => 1
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.concat([Pet.new(name: 'Brain'), Pet.new(name: 'Benny')])
-
# person.pets.size # => 5
-
1
def concat(*records)
-
@association.concat(*records)
-
end
-
-
# Replaces this collection with +other_array+. This will perform a diff
-
# and delete/add only records that have changed.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets
-
# # => [#<Pet id: 1, name: "Gorby", group: "cats", person_id: 1>]
-
#
-
# other_pets = [Pet.new(name: 'Puff', group: 'celebrities']
-
#
-
# person.pets.replace(other_pets)
-
#
-
# person.pets
-
# # => [#<Pet id: 2, name: "Puff", group: "celebrities", person_id: 1>]
-
#
-
# If the supplied array has an incorrect association type, it raises
-
# an <tt>ActiveRecord::AssociationTypeMismatch</tt> error:
-
#
-
# person.pets.replace(["doo", "ggie", "gaga"])
-
# # => ActiveRecord::AssociationTypeMismatch: Pet expected, got String
-
1
def replace(other_array)
-
@association.replace(other_array)
-
end
-
-
# Deletes all the records from the collection according to the strategy
-
# specified by the +:dependent+ option. If no +:dependent+ option is given,
-
# then it will follow the default strategy.
-
#
-
# For +has_many :through+ associations, the default deletion strategy is
-
# +:delete_all+.
-
#
-
# For +has_many+ associations, the default deletion strategy is +:nullify+.
-
# This sets the foreign keys to +NULL+.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets # dependent: :nullify option by default
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.delete_all
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.size # => 0
-
# person.pets # => []
-
#
-
# Pet.find(1, 2, 3)
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: nil>,
-
# # #<Pet id: 2, name: "Spook", person_id: nil>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: nil>
-
# # ]
-
#
-
# Both +has_many+ and +has_many :through+ dependencies default to the
-
# +:delete_all+ strategy if the +:dependent+ option is set to +:destroy+.
-
# Records are not instantiated and callbacks will not be fired.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets, dependent: :destroy
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.delete_all
-
#
-
# Pet.find(1, 2, 3)
-
# # => ActiveRecord::RecordNotFound
-
#
-
# If it is set to <tt>:delete_all</tt>, all the objects are deleted
-
# *without* calling their +destroy+ method.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets, dependent: :delete_all
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.delete_all
-
#
-
# Pet.find(1, 2, 3)
-
# # => ActiveRecord::RecordNotFound
-
1
def delete_all(dependent = nil)
-
@association.delete_all(dependent)
-
end
-
-
# Deletes the records of the collection directly from the database
-
# ignoring the +:dependent+ option. Records are instantiated and it
-
# invokes +before_remove+, +after_remove+ , +before_destroy+ and
-
# +after_destroy+ callbacks.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.destroy_all
-
#
-
# person.pets.size # => 0
-
# person.pets # => []
-
#
-
# Pet.find(1) # => Couldn't find Pet with id=1
-
1
def destroy_all
-
@association.destroy_all
-
end
-
-
# Deletes the +records+ supplied from the collection according to the strategy
-
# specified by the +:dependent+ option. If no +:dependent+ option is given,
-
# then it will follow the default strategy. Returns an array with the
-
# deleted records.
-
#
-
# For +has_many :through+ associations, the default deletion strategy is
-
# +:delete_all+.
-
#
-
# For +has_many+ associations, the default deletion strategy is +:nullify+.
-
# This sets the foreign keys to +NULL+.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets # dependent: :nullify option by default
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.delete(Pet.find(1))
-
# # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
-
#
-
# person.pets.size # => 2
-
# person.pets
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# Pet.find(1)
-
# # => #<Pet id: 1, name: "Fancy-Fancy", person_id: nil>
-
#
-
# If it is set to <tt>:destroy</tt> all the +records+ are removed by calling
-
# their +destroy+ method. See +destroy+ for more information.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets, dependent: :destroy
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.delete(Pet.find(1), Pet.find(3))
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.size # => 1
-
# person.pets
-
# # => [#<Pet id: 2, name: "Spook", person_id: 1>]
-
#
-
# Pet.find(1, 3)
-
# # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (1, 3)
-
#
-
# If it is set to <tt>:delete_all</tt>, all the +records+ are deleted
-
# *without* calling their +destroy+ method.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets, dependent: :delete_all
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.delete(Pet.find(1))
-
# # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
-
#
-
# person.pets.size # => 2
-
# person.pets
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# Pet.find(1)
-
# # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=1
-
#
-
# You can pass +Fixnum+ or +String+ values, it finds the records
-
# responding to the +id+ and executes delete on them.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.delete("1")
-
# # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
-
#
-
# person.pets.delete(2, 3)
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
1
def delete(*records)
-
@association.delete(*records)
-
end
-
-
# Destroys the +records+ supplied and removes them from the collection.
-
# This method will _always_ remove record from the database ignoring
-
# the +:dependent+ option. Returns an array with the removed records.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.destroy(Pet.find(1))
-
# # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
-
#
-
# person.pets.size # => 2
-
# person.pets
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.destroy(Pet.find(2), Pet.find(3))
-
# # => [
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.size # => 0
-
# person.pets # => []
-
#
-
# Pet.find(1, 2, 3) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (1, 2, 3)
-
#
-
# You can pass +Fixnum+ or +String+ values, it finds the records
-
# responding to the +id+ and then deletes them from the database.
-
#
-
# person.pets.size # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 4, name: "Benny", person_id: 1>,
-
# # #<Pet id: 5, name: "Brain", person_id: 1>,
-
# # #<Pet id: 6, name: "Boss", person_id: 1>
-
# # ]
-
#
-
# person.pets.destroy("4")
-
# # => #<Pet id: 4, name: "Benny", person_id: 1>
-
#
-
# person.pets.size # => 2
-
# person.pets
-
# # => [
-
# # #<Pet id: 5, name: "Brain", person_id: 1>,
-
# # #<Pet id: 6, name: "Boss", person_id: 1>
-
# # ]
-
#
-
# person.pets.destroy(5, 6)
-
# # => [
-
# # #<Pet id: 5, name: "Brain", person_id: 1>,
-
# # #<Pet id: 6, name: "Boss", person_id: 1>
-
# # ]
-
#
-
# person.pets.size # => 0
-
# person.pets # => []
-
#
-
# Pet.find(4, 5, 6) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (4, 5, 6)
-
1
def destroy(*records)
-
@association.destroy(*records)
-
end
-
-
# Specifies whether the records should be unique or not.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.select(:name)
-
# # => [
-
# # #<Pet name: "Fancy-Fancy">,
-
# # #<Pet name: "Fancy-Fancy">
-
# # ]
-
#
-
# person.pets.select(:name).distinct
-
# # => [#<Pet name: "Fancy-Fancy">]
-
1
def distinct
-
@association.distinct
-
end
-
1
alias uniq distinct
-
-
# Count all records using SQL.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.count # => 3
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
1
def count(column_name = nil, options = {})
-
# TODO: Remove options argument as soon we remove support to
-
# activerecord-deprecated_finders.
-
@association.count(column_name, options)
-
end
-
-
# Returns the size of the collection. If the collection hasn't been loaded,
-
# it executes a <tt>SELECT COUNT(*)</tt> query. Else it calls <tt>collection.size</tt>.
-
#
-
# If the collection has been already loaded +size+ and +length+ are
-
# equivalent. If not and you are going to need the records anyway
-
# +length+ will take one less query. Otherwise +size+ is more efficient.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.size # => 3
-
# # executes something like SELECT COUNT(*) FROM "pets" WHERE "pets"."person_id" = 1
-
#
-
# person.pets # This will execute a SELECT * FROM query
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
#
-
# person.pets.size # => 3
-
# # Because the collection is already loaded, this will behave like
-
# # collection.size and no SQL count query is executed.
-
1
def size
-
@association.size
-
end
-
-
# Returns the size of the collection calling +size+ on the target.
-
# If the collection has been already loaded, +length+ and +size+ are
-
# equivalent. If not and you are going to need the records anyway this
-
# method will take one less query. Otherwise +size+ is more efficient.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.length # => 3
-
# # executes something like SELECT "pets".* FROM "pets" WHERE "pets"."person_id" = 1
-
#
-
# # Because the collection is loaded, you can
-
# # call the collection with no additional queries:
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
1
def length
-
@association.length
-
end
-
-
# Returns +true+ if the collection is empty. If the collection has been
-
# loaded it is equivalent
-
# to <tt>collection.size.zero?</tt>. If the collection has not been loaded,
-
# it is equivalent to <tt>collection.exists?</tt>. If the collection has
-
# not already been loaded and you are going to fetch the records anyway it
-
# is better to check <tt>collection.length.zero?</tt>.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.count # => 1
-
# person.pets.empty? # => false
-
#
-
# person.pets.delete_all
-
#
-
# person.pets.count # => 0
-
# person.pets.empty? # => true
-
1
def empty?
-
@association.empty?
-
end
-
-
# Returns +true+ if the collection is not empty.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.count # => 0
-
# person.pets.any? # => false
-
#
-
# person.pets << Pet.new(name: 'Snoop')
-
# person.pets.count # => 0
-
# person.pets.any? # => true
-
#
-
# You can also pass a +block+ to define criteria. The behavior
-
# is the same, it returns true if the collection based on the
-
# criteria is not empty.
-
#
-
# person.pets
-
# # => [#<Pet name: "Snoop", group: "dogs">]
-
#
-
# person.pets.any? do |pet|
-
# pet.group == 'cats'
-
# end
-
# # => false
-
#
-
# person.pets.any? do |pet|
-
# pet.group == 'dogs'
-
# end
-
# # => true
-
1
def any?(&block)
-
@association.any?(&block)
-
end
-
-
# Returns true if the collection has more than one record.
-
# Equivalent to <tt>collection.size > 1</tt>.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.count # => 1
-
# person.pets.many? # => false
-
#
-
# person.pets << Pet.new(name: 'Snoopy')
-
# person.pets.count # => 2
-
# person.pets.many? # => true
-
#
-
# You can also pass a +block+ to define criteria. The
-
# behavior is the same, it returns true if the collection
-
# based on the criteria has more than one record.
-
#
-
# person.pets
-
# # => [
-
# # #<Pet name: "Gorby", group: "cats">,
-
# # #<Pet name: "Puff", group: "cats">,
-
# # #<Pet name: "Snoop", group: "dogs">
-
# # ]
-
#
-
# person.pets.many? do |pet|
-
# pet.group == 'dogs'
-
# end
-
# # => false
-
#
-
# person.pets.many? do |pet|
-
# pet.group == 'cats'
-
# end
-
# # => true
-
1
def many?(&block)
-
@association.many?(&block)
-
end
-
-
# Returns +true+ if the given +record+ is present in the collection.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets # => [#<Pet id: 20, name: "Snoop">]
-
#
-
# person.pets.include?(Pet.find(20)) # => true
-
# person.pets.include?(Pet.find(21)) # => false
-
1
def include?(record)
-
!!@association.include?(record)
-
end
-
-
1
def arel
-
scope.arel
-
end
-
-
1
def proxy_association
-
@association
-
end
-
-
# We don't want this object to be put on the scoping stack, because
-
# that could create an infinite loop where we call an @association
-
# method, which gets the current scope, which is this object, which
-
# delegates to @association, and so on.
-
1
def scoping
-
@association.scope.scoping { yield }
-
end
-
-
# Returns a <tt>Relation</tt> object for the records in this association
-
1
def scope
-
@association.scope
-
end
-
1
alias spawn scope
-
-
# Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays
-
# contain the same number of elements and if each element is equal
-
# to the corresponding element in the +other+ array, otherwise returns
-
# +false+.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>
-
# # ]
-
#
-
# other = person.pets.to_ary
-
#
-
# person.pets == other
-
# # => true
-
#
-
# other = [Pet.new(id: 1), Pet.new(id: 2)]
-
#
-
# person.pets == other
-
# # => false
-
1
def ==(other)
-
load_target == other
-
end
-
-
# Returns a new array of objects from the collection. If the collection
-
# hasn't been loaded, it fetches the records from the database.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets
-
# # => [
-
# # #<Pet id: 4, name: "Benny", person_id: 1>,
-
# # #<Pet id: 5, name: "Brain", person_id: 1>,
-
# # #<Pet id: 6, name: "Boss", person_id: 1>
-
# # ]
-
#
-
# other_pets = person.pets.to_ary
-
# # => [
-
# # #<Pet id: 4, name: "Benny", person_id: 1>,
-
# # #<Pet id: 5, name: "Brain", person_id: 1>,
-
# # #<Pet id: 6, name: "Boss", person_id: 1>
-
# # ]
-
#
-
# other_pets.replace([Pet.new(name: 'BooGoo')])
-
#
-
# other_pets
-
# # => [#<Pet id: nil, name: "BooGoo", person_id: 1>]
-
#
-
# person.pets
-
# # This is not affected by replace
-
# # => [
-
# # #<Pet id: 4, name: "Benny", person_id: 1>,
-
# # #<Pet id: 5, name: "Brain", person_id: 1>,
-
# # #<Pet id: 6, name: "Boss", person_id: 1>
-
# # ]
-
1
def to_ary
-
load_target.dup
-
end
-
1
alias_method :to_a, :to_ary
-
-
# Adds one or more +records+ to the collection by setting their foreign keys
-
# to the association's primary key. Returns +self+, so several appends may be
-
# chained together.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets.size # => 0
-
# person.pets << Pet.new(name: 'Fancy-Fancy')
-
# person.pets << [Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo')]
-
# person.pets.size # => 3
-
#
-
# person.id # => 1
-
# person.pets
-
# # => [
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
-
# # ]
-
1
def <<(*records)
-
proxy_association.concat(records) && self
-
end
-
1
alias_method :push, :<<
-
1
alias_method :append, :<<
-
-
1
def prepend(*args)
-
raise NoMethodError, "prepend on association is not defined. Please use << or append"
-
end
-
-
# Equivalent to +delete_all+. The difference is that returns +self+, instead
-
# of an array with the deleted objects, so methods can be chained. See
-
# +delete_all+ for more information.
-
1
def clear
-
delete_all
-
self
-
end
-
-
# Reloads the collection from the database. Returns +self+.
-
# Equivalent to <tt>collection(true)</tt>.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets # fetches pets from the database
-
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
-
#
-
# person.pets # uses the pets cache
-
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
-
#
-
# person.pets.reload # fetches pets from the database
-
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
-
#
-
# person.pets(true) # fetches pets from the database
-
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
-
1
def reload
-
proxy_association.reload
-
self
-
end
-
-
# Unloads the association. Returns +self+.
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :pets
-
# end
-
#
-
# person.pets # fetches pets from the database
-
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
-
#
-
# person.pets # uses the pets cache
-
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
-
#
-
# person.pets.reset # clears the pets cache
-
#
-
# person.pets # fetches pets from the database
-
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
-
1
def reset
-
proxy_association.reset
-
proxy_association.reset_scope
-
self
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Associations
-
1
class JoinDependency # :nodoc:
-
1
autoload :JoinBase, 'active_record/associations/join_dependency/join_base'
-
1
autoload :JoinAssociation, 'active_record/associations/join_dependency/join_association'
-
-
1
class Aliases # :nodoc:
-
1
def initialize(tables)
-
@tables = tables
-
@alias_cache = tables.each_with_object({}) { |table,h|
-
h[table.node] = table.columns.each_with_object({}) { |column,i|
-
i[column.name] = column.alias
-
}
-
}
-
@name_and_alias_cache = tables.each_with_object({}) { |table,h|
-
h[table.node] = table.columns.map { |column|
-
[column.name, column.alias]
-
}
-
}
-
end
-
-
1
def columns
-
@tables.flat_map { |t| t.column_aliases }
-
end
-
-
# An array of [column_name, alias] pairs for the table
-
1
def column_aliases(node)
-
@name_and_alias_cache[node]
-
end
-
-
1
def column_alias(node, column)
-
@alias_cache[node][column]
-
end
-
-
1
class Table < Struct.new(:node, :columns)
-
1
def table
-
Arel::Nodes::TableAlias.new node.table, node.aliased_table_name
-
end
-
-
1
def column_aliases
-
t = table
-
columns.map { |column| t[column.name].as Arel.sql column.alias }
-
end
-
end
-
1
Column = Struct.new(:name, :alias)
-
end
-
-
1
attr_reader :alias_tracker, :base_klass, :join_root
-
-
1
def self.make_tree(associations)
-
72
hash = {}
-
72
walk_tree associations, hash
-
72
hash
-
end
-
-
1
def self.walk_tree(associations, hash)
-
72
case associations
-
when Symbol, String
-
hash[associations.to_sym] ||= {}
-
when Array
-
72
associations.each do |assoc|
-
walk_tree assoc, hash
-
end
-
when Hash
-
associations.each do |k,v|
-
cache = hash[k] ||= {}
-
walk_tree v, cache
-
end
-
else
-
raise ConfigurationError, associations.inspect
-
end
-
end
-
-
# base is the base class on which operation is taking place.
-
# associations is the list of associations which are joined using hash, symbol or array.
-
# joins is the list of all string join commands and arel nodes.
-
#
-
# Example :
-
#
-
# class Physician < ActiveRecord::Base
-
# has_many :appointments
-
# has_many :patients, through: :appointments
-
# end
-
#
-
# If I execute `@physician.patients.to_a` then
-
# base # => Physician
-
# associations # => []
-
# joins # => [#<Arel::Nodes::InnerJoin: ...]
-
#
-
# However if I execute `Physician.joins(:appointments).to_a` then
-
# base # => Physician
-
# associations # => [:appointments]
-
# joins # => []
-
#
-
1
def initialize(base, associations, joins)
-
72
@alias_tracker = AliasTracker.create(base.connection, joins)
-
72
@alias_tracker.aliased_table_for(base.table_name, base.table_name) # Updates the count for base.table_name to 1
-
72
tree = self.class.make_tree associations
-
72
@join_root = JoinBase.new base, build(tree, base)
-
72
@join_root.children.each { |child| construct_tables! @join_root, child }
-
end
-
-
1
def reflections
-
36
join_root.drop(1).map!(&:reflection)
-
end
-
-
1
def join_constraints(outer_joins)
-
36
joins = join_root.children.flat_map { |child|
-
make_inner_joins join_root, child
-
}
-
-
36
joins.concat outer_joins.flat_map { |oj|
-
36
if join_root.match? oj.join_root
-
36
walk join_root, oj.join_root
-
else
-
oj.join_root.children.flat_map { |child|
-
make_outer_joins oj.join_root, child
-
}
-
end
-
}
-
end
-
-
1
def aliases
-
Aliases.new join_root.each_with_index.map { |join_part,i|
-
columns = join_part.column_names.each_with_index.map { |column_name,j|
-
Aliases::Column.new column_name, "t#{i}_r#{j}"
-
}
-
Aliases::Table.new(join_part, columns)
-
}
-
end
-
-
1
def instantiate(result_set, aliases)
-
primary_key = aliases.column_alias(join_root, join_root.primary_key)
-
-
seen = Hash.new { |h,parent_klass|
-
h[parent_klass] = Hash.new { |i,parent_id|
-
i[parent_id] = Hash.new { |j,child_klass| j[child_klass] = {} }
-
}
-
}
-
-
model_cache = Hash.new { |h,klass| h[klass] = {} }
-
parents = model_cache[join_root]
-
column_aliases = aliases.column_aliases join_root
-
-
message_bus = ActiveSupport::Notifications.instrumenter
-
-
payload = {
-
record_count: result_set.length,
-
class_name: join_root.base_klass.name
-
}
-
-
message_bus.instrument('instantiation.active_record', payload) do
-
result_set.each { |row_hash|
-
parent_key = primary_key ? row_hash[primary_key] : row_hash
-
parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases)
-
construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
-
}
-
end
-
-
parents.values
-
end
-
-
1
private
-
-
1
def make_constraints(parent, child, tables, join_type)
-
chain = child.reflection.chain
-
foreign_table = parent.table
-
foreign_klass = parent.base_klass
-
child.join_constraints(foreign_table, foreign_klass, child, join_type, tables, child.reflection.scope_chain, chain)
-
end
-
-
1
def make_outer_joins(parent, child)
-
tables = table_aliases_for(parent, child)
-
join_type = Arel::Nodes::OuterJoin
-
info = make_constraints parent, child, tables, join_type
-
-
[info] + child.children.flat_map { |c| make_outer_joins(child, c) }
-
end
-
-
1
def make_inner_joins(parent, child)
-
tables = child.tables
-
join_type = Arel::Nodes::InnerJoin
-
info = make_constraints parent, child, tables, join_type
-
-
[info] + child.children.flat_map { |c| make_inner_joins(child, c) }
-
end
-
-
1
def table_aliases_for(parent, node)
-
node.reflection.chain.map { |reflection|
-
alias_tracker.aliased_table_for(
-
reflection.table_name,
-
table_alias_for(reflection, parent, reflection != node.reflection)
-
)
-
}
-
end
-
-
1
def construct_tables!(parent, node)
-
node.tables = table_aliases_for(parent, node)
-
node.children.each { |child| construct_tables! node, child }
-
end
-
-
1
def table_alias_for(reflection, parent, join)
-
name = "#{reflection.plural_name}_#{parent.table_name}"
-
name << "_join" if join
-
name
-
end
-
-
1
def walk(left, right)
-
36
intersection, missing = right.children.map { |node1|
-
[left.children.find { |node2| node1.match? node2 }, node1]
-
}.partition(&:first)
-
-
36
ojs = missing.flat_map { |_,n| make_outer_joins left, n }
-
36
intersection.flat_map { |l,r| walk l, r }.concat ojs
-
end
-
-
1
def find_reflection(klass, name)
-
klass._reflect_on_association(name) or
-
raise ConfigurationError, "Association named '#{ name }' was not found on #{ klass.name }; perhaps you misspelled it?"
-
end
-
-
1
def build(associations, base_klass)
-
72
associations.map do |name, right|
-
reflection = find_reflection base_klass, name
-
reflection.check_validity!
-
reflection.check_eager_loadable!
-
-
if reflection.polymorphic?
-
raise EagerLoadPolymorphicError.new(reflection)
-
end
-
-
JoinAssociation.new reflection, build(right, reflection.klass)
-
end
-
end
-
-
1
def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
-
return if ar_parent.nil?
-
primary_id = ar_parent.id
-
-
parent.children.each do |node|
-
if node.reflection.collection?
-
other = ar_parent.association(node.reflection.name)
-
other.loaded!
-
else
-
if ar_parent.association_cache.key?(node.reflection.name)
-
model = ar_parent.association(node.reflection.name).target
-
construct(model, node, row, rs, seen, model_cache, aliases)
-
next
-
end
-
end
-
-
key = aliases.column_alias(node, node.primary_key)
-
id = row[key]
-
if id.nil?
-
nil_association = ar_parent.association(node.reflection.name)
-
nil_association.loaded!
-
next
-
end
-
-
model = seen[parent.base_klass][primary_id][node.base_klass][id]
-
-
if model
-
construct(model, node, row, rs, seen, model_cache, aliases)
-
else
-
model = construct_model(ar_parent, node, row, model_cache, id, aliases)
-
seen[parent.base_klass][primary_id][node.base_klass][id] = model
-
construct(model, node, row, rs, seen, model_cache, aliases)
-
end
-
end
-
end
-
-
1
def construct_model(record, node, row, model_cache, id, aliases)
-
model = model_cache[node][id] ||= node.instantiate(row,
-
aliases.column_aliases(node))
-
other = record.association(node.reflection.name)
-
-
if node.reflection.collection?
-
other.target.push(model)
-
else
-
other.target = model
-
end
-
-
other.set_inverse_instance(model)
-
model
-
end
-
end
-
end
-
end
-
1
require 'active_record/associations/join_dependency/join_part'
-
-
1
module ActiveRecord
-
1
module Associations
-
1
class JoinDependency # :nodoc:
-
1
class JoinBase < JoinPart # :nodoc:
-
1
def match?(other)
-
36
return true if self == other
-
36
super && base_klass == other.base_klass
-
end
-
-
1
def table
-
base_klass.arel_table
-
end
-
-
1
def aliased_table_name
-
base_klass.table_name
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Associations
-
1
class JoinDependency # :nodoc:
-
# A JoinPart represents a part of a JoinDependency. It is inherited
-
# by JoinBase and JoinAssociation. A JoinBase represents the Active Record which
-
# everything else is being joined onto. A JoinAssociation represents an association which
-
# is joining to the base. A JoinAssociation may result in more than one actual join
-
# operations (for example a has_and_belongs_to_many JoinAssociation would result in
-
# two; one for the join table and one for the target table).
-
1
class JoinPart # :nodoc:
-
1
include Enumerable
-
-
# The Active Record class which this join part is associated 'about'; for a JoinBase
-
# this is the actual base model, for a JoinAssociation this is the target model of the
-
# association.
-
1
attr_reader :base_klass, :children
-
-
1
delegate :table_name, :column_names, :primary_key, :to => :base_klass
-
-
1
def initialize(base_klass, children)
-
72
@base_klass = base_klass
-
72
@children = children
-
end
-
-
1
def name
-
reflection.name
-
end
-
-
1
def match?(other)
-
36
self.class == other.class
-
end
-
-
1
def each(&block)
-
36
yield self
-
36
children.each { |child| child.each(&block) }
-
end
-
-
# An Arel::Table for the active_record
-
1
def table
-
raise NotImplementedError
-
end
-
-
# The alias for the active_record's table
-
1
def aliased_table_name
-
raise NotImplementedError
-
end
-
-
1
def extract_record(row, column_names_with_alias)
-
# This code is performance critical as it is called per row.
-
# see: https://github.com/rails/rails/pull/12185
-
hash = {}
-
-
index = 0
-
length = column_names_with_alias.length
-
-
while index < length
-
column_name, alias_name = column_names_with_alias[index]
-
hash[column_name] = row[alias_name]
-
index += 1
-
end
-
-
hash
-
end
-
-
1
def instantiate(row, aliases)
-
base_klass.instantiate(extract_record(row, aliases))
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Associations
-
# Implements the details of eager loading of Active Record associations.
-
#
-
# Suppose that you have the following two Active Record models:
-
#
-
# class Author < ActiveRecord::Base
-
# # columns: name, age
-
# has_many :books
-
# end
-
#
-
# class Book < ActiveRecord::Base
-
# # columns: title, sales, author_id
-
# end
-
#
-
# When you load an author with all associated books Active Record will make
-
# multiple queries like this:
-
#
-
# Author.includes(:books).where(name: ['bell hooks', 'Homer']).to_a
-
#
-
# => SELECT `authors`.* FROM `authors` WHERE `name` IN ('bell hooks', 'Homer')
-
# => SELECT `books`.* FROM `books` WHERE `author_id` IN (2, 5)
-
#
-
# Active Record saves the ids of the records from the first query to use in
-
# the second. Depending on the number of associations involved there can be
-
# arbitrarily many SQL queries made.
-
#
-
# However, if there is a WHERE clause that spans across tables Active
-
# Record will fall back to a slightly more resource-intensive single query:
-
#
-
# Author.includes(:books).where(books: {title: 'Illiad'}).to_a
-
# => SELECT `authors`.`id` AS t0_r0, `authors`.`name` AS t0_r1, `authors`.`age` AS t0_r2,
-
# `books`.`id` AS t1_r0, `books`.`title` AS t1_r1, `books`.`sales` AS t1_r2
-
# FROM `authors`
-
# LEFT OUTER JOIN `books` ON `authors`.`id` = `books`.`author_id`
-
# WHERE `books`.`title` = 'Illiad'
-
#
-
# This could result in many rows that contain redundant data and it performs poorly at scale
-
# and is therefore only used when necessary.
-
#
-
1
class Preloader #:nodoc:
-
1
extend ActiveSupport::Autoload
-
-
1
eager_autoload do
-
1
autoload :Association, 'active_record/associations/preloader/association'
-
1
autoload :SingularAssociation, 'active_record/associations/preloader/singular_association'
-
1
autoload :CollectionAssociation, 'active_record/associations/preloader/collection_association'
-
1
autoload :ThroughAssociation, 'active_record/associations/preloader/through_association'
-
-
1
autoload :HasMany, 'active_record/associations/preloader/has_many'
-
1
autoload :HasManyThrough, 'active_record/associations/preloader/has_many_through'
-
1
autoload :HasOne, 'active_record/associations/preloader/has_one'
-
1
autoload :HasOneThrough, 'active_record/associations/preloader/has_one_through'
-
1
autoload :BelongsTo, 'active_record/associations/preloader/belongs_to'
-
end
-
-
# Eager loads the named associations for the given Active Record record(s).
-
#
-
# In this description, 'association name' shall refer to the name passed
-
# to an association creation method. For example, a model that specifies
-
# <tt>belongs_to :author</tt>, <tt>has_many :buyers</tt> has association
-
# names +:author+ and +:buyers+.
-
#
-
# == Parameters
-
# +records+ is an array of ActiveRecord::Base. This array needs not be flat,
-
# i.e. +records+ itself may also contain arrays of records. In any case,
-
# +preload_associations+ will preload the all associations records by
-
# flattening +records+.
-
#
-
# +associations+ specifies one or more associations that you want to
-
# preload. It may be:
-
# - a Symbol or a String which specifies a single association name. For
-
# example, specifying +:books+ allows this method to preload all books
-
# for an Author.
-
# - an Array which specifies multiple association names. This array
-
# is processed recursively. For example, specifying <tt>[:avatar, :books]</tt>
-
# allows this method to preload an author's avatar as well as all of his
-
# books.
-
# - a Hash which specifies multiple association names, as well as
-
# association names for the to-be-preloaded association objects. For
-
# example, specifying <tt>{ author: :avatar }</tt> will preload a
-
# book's author, as well as that author's avatar.
-
#
-
# +:associations+ has the same format as the +:include+ option for
-
# <tt>ActiveRecord::Base.find</tt>. So +associations+ could look like this:
-
#
-
# :books
-
# [ :books, :author ]
-
# { author: :avatar }
-
# [ :books, { author: :avatar } ]
-
-
1
NULL_RELATION = Struct.new(:values, :bind_values).new({}, [])
-
-
1
def preload(records, associations, preload_scope = nil)
-
records = Array.wrap(records).compact.uniq
-
associations = Array.wrap(associations)
-
preload_scope = preload_scope || NULL_RELATION
-
-
if records.empty?
-
[]
-
else
-
associations.flat_map { |association|
-
preloaders_on association, records, preload_scope
-
}
-
end
-
end
-
-
1
private
-
-
1
def preloaders_on(association, records, scope)
-
case association
-
when Hash
-
preloaders_for_hash(association, records, scope)
-
when Symbol
-
preloaders_for_one(association, records, scope)
-
when String
-
preloaders_for_one(association.to_sym, records, scope)
-
else
-
raise ArgumentError, "#{association.inspect} was not recognised for preload"
-
end
-
end
-
-
1
def preloaders_for_hash(association, records, scope)
-
association.flat_map { |parent, child|
-
loaders = preloaders_for_one parent, records, scope
-
-
recs = loaders.flat_map(&:preloaded_records).uniq
-
loaders.concat Array.wrap(child).flat_map { |assoc|
-
preloaders_on assoc, recs, scope
-
}
-
loaders
-
}
-
end
-
-
# Not all records have the same class, so group then preload group on the reflection
-
# itself so that if various subclass share the same association then we do not split
-
# them unnecessarily
-
#
-
# Additionally, polymorphic belongs_to associations can have multiple associated
-
# classes, depending on the polymorphic_type field. So we group by the classes as
-
# well.
-
1
def preloaders_for_one(association, records, scope)
-
grouped_records(association, records).flat_map do |reflection, klasses|
-
klasses.map do |rhs_klass, rs|
-
loader = preloader_for(reflection, rs, rhs_klass).new(rhs_klass, rs, reflection, scope)
-
loader.run self
-
loader
-
end
-
end
-
end
-
-
1
def grouped_records(association, records)
-
h = {}
-
records.each do |record|
-
next unless record
-
assoc = record.association(association)
-
klasses = h[assoc.reflection] ||= {}
-
(klasses[assoc.klass] ||= []) << record
-
end
-
h
-
end
-
-
1
class AlreadyLoaded # :nodoc:
-
1
attr_reader :owners, :reflection
-
-
1
def initialize(klass, owners, reflection, preload_scope)
-
@owners = owners
-
@reflection = reflection
-
end
-
-
1
def run(preloader); end
-
-
1
def preloaded_records
-
owners.flat_map { |owner| owner.association(reflection.name).target }
-
end
-
end
-
-
1
class NullPreloader # :nodoc:
-
1
def self.new(klass, owners, reflection, preload_scope); self; end
-
1
def self.run(preloader); end
-
1
def self.preloaded_records; []; end
-
end
-
-
1
def preloader_for(reflection, owners, rhs_klass)
-
return NullPreloader unless rhs_klass
-
-
if owners.first.association(reflection.name).loaded?
-
return AlreadyLoaded
-
end
-
reflection.check_preloadable!
-
-
case reflection.macro
-
when :has_many
-
reflection.options[:through] ? HasManyThrough : HasMany
-
when :has_one
-
reflection.options[:through] ? HasOneThrough : HasOne
-
when :belongs_to
-
BelongsTo
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_model/forbidden_attributes_protection'
-
-
1
module ActiveRecord
-
1
module AttributeAssignment
-
1
extend ActiveSupport::Concern
-
1
include ActiveModel::ForbiddenAttributesProtection
-
-
# Allows you to set all the attributes by passing in a hash of attributes with
-
# keys matching the attribute names (which again matches the column names).
-
#
-
# If the passed hash responds to <tt>permitted?</tt> method and the return value
-
# of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
-
# exception is raised.
-
#
-
# cat = Cat.new(name: "Gorby", status: "yawning")
-
# cat.attributes # => { "name" => "Gorby", "status" => "yawning", "created_at" => nil, "updated_at" => nil}
-
# cat.assign_attributes(status: "sleeping")
-
# cat.attributes # => { "name" => "Gorby", "status" => "sleeping", "created_at" => nil, "updated_at" => nil }
-
#
-
# New attributes will be persisted in the database when the object is saved.
-
#
-
# Aliased to <tt>attributes=</tt>.
-
1
def assign_attributes(new_attributes)
-
15
if !new_attributes.respond_to?(:stringify_keys)
-
raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
-
end
-
15
return if new_attributes.blank?
-
-
15
attributes = new_attributes.stringify_keys
-
15
multi_parameter_attributes = []
-
15
nested_parameter_attributes = []
-
-
15
attributes = sanitize_for_mass_assignment(attributes)
-
-
15
attributes.each do |k, v|
-
150
if k.include?("(")
-
multi_parameter_attributes << [ k, v ]
-
elsif v.is_a?(Hash)
-
nested_parameter_attributes << [ k, v ]
-
else
-
150
_assign_attribute(k, v)
-
end
-
end
-
-
15
assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
-
15
assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
-
end
-
-
1
alias attributes= assign_attributes
-
-
1
private
-
-
1
def _assign_attribute(k, v)
-
150
public_send("#{k}=", v)
-
rescue NoMethodError, NameError
-
if respond_to?("#{k}=")
-
raise
-
else
-
raise UnknownAttributeError.new(self, k)
-
end
-
end
-
-
# Assign any deferred nested attributes after the base attributes have been set.
-
1
def assign_nested_parameter_attributes(pairs)
-
pairs.each { |k, v| _assign_attribute(k, v) }
-
end
-
-
# Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
-
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
-
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
-
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
-
# parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum and
-
# f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
-
1
def assign_multiparameter_attributes(pairs)
-
execute_callstack_for_multiparameter_attributes(
-
extract_callstack_for_multiparameter_attributes(pairs)
-
)
-
end
-
-
1
def execute_callstack_for_multiparameter_attributes(callstack)
-
errors = []
-
callstack.each do |name, values_with_empty_parameters|
-
begin
-
send("#{name}=", MultiparameterAttribute.new(self, name, values_with_empty_parameters).read_value)
-
rescue => ex
-
errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
-
end
-
end
-
unless errors.empty?
-
error_descriptions = errors.map { |ex| ex.message }.join(",")
-
raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
-
end
-
end
-
-
1
def extract_callstack_for_multiparameter_attributes(pairs)
-
attributes = {}
-
-
pairs.each do |(multiparameter_name, value)|
-
attribute_name = multiparameter_name.split("(").first
-
attributes[attribute_name] ||= {}
-
-
parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
-
attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
-
end
-
-
attributes
-
end
-
-
1
def type_cast_attribute_value(multiparameter_name, value)
-
multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
-
end
-
-
1
def find_parameter_position(multiparameter_name)
-
multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
-
end
-
-
1
class MultiparameterAttribute #:nodoc:
-
1
attr_reader :object, :name, :values, :cast_type
-
-
1
def initialize(object, name, values)
-
@object = object
-
@name = name
-
@values = values
-
end
-
-
1
def read_value
-
return if values.values.compact.empty?
-
-
@cast_type = object.type_for_attribute(name)
-
klass = cast_type.klass
-
-
if klass == Time
-
read_time
-
elsif klass == Date
-
read_date
-
else
-
read_other
-
end
-
end
-
-
1
private
-
-
1
def instantiate_time_object(set_values)
-
if object.class.send(:create_time_zone_conversion_attribute?, name, cast_type)
-
Time.zone.local(*set_values)
-
else
-
Time.send(object.class.default_timezone, *set_values)
-
end
-
end
-
-
1
def read_time
-
# If column is a :time (and not :date or :datetime) there is no need to validate if
-
# there are year/month/day fields
-
if cast_type.type == :time
-
# if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil
-
{ 1 => 1970, 2 => 1, 3 => 1 }.each do |key,value|
-
values[key] ||= value
-
end
-
else
-
# else column is a timestamp, so if Date bits were not provided, error
-
validate_required_parameters!([1,2,3])
-
-
# If Date bits were provided but blank, then return nil
-
return if blank_date_parameter?
-
end
-
-
max_position = extract_max_param(6)
-
set_values = values.values_at(*(1..max_position))
-
# If Time bits are not there, then default to 0
-
(3..5).each { |i| set_values[i] = set_values[i].presence || 0 }
-
instantiate_time_object(set_values)
-
end
-
-
1
def read_date
-
return if blank_date_parameter?
-
set_values = values.values_at(1,2,3)
-
begin
-
Date.new(*set_values)
-
rescue ArgumentError # if Date.new raises an exception on an invalid date
-
instantiate_time_object(set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
-
end
-
end
-
-
1
def read_other
-
max_position = extract_max_param
-
positions = (1..max_position)
-
validate_required_parameters!(positions)
-
-
values.slice(*positions)
-
end
-
-
# Checks whether some blank date parameter exists. Note that this is different
-
# than the validate_required_parameters! method, since it just checks for blank
-
# positions instead of missing ones, and does not raise in case one blank position
-
# exists. The caller is responsible to handle the case of this returning true.
-
1
def blank_date_parameter?
-
(1..3).any? { |position| values[position].blank? }
-
end
-
-
# If some position is not provided, it errors out a missing parameter exception.
-
1
def validate_required_parameters!(positions)
-
if missing_parameter = positions.detect { |position| !values.key?(position) }
-
raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter})")
-
end
-
end
-
-
1
def extract_max_param(upper_cap = 100)
-
[values.keys.max, upper_cap].min
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module AttributeDecorators # :nodoc:
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
class_attribute :attribute_type_decorations, instance_accessor: false # :internal:
-
1
self.attribute_type_decorations = TypeDecorator.new
-
end
-
-
1
module ClassMethods # :nodoc:
-
1
def decorate_attribute_type(column_name, decorator_name, &block)
-
matcher = ->(name, _) { name == column_name.to_s }
-
key = "_#{column_name}_#{decorator_name}"
-
decorate_matching_attribute_types(matcher, key, &block)
-
end
-
-
1
def decorate_matching_attribute_types(matcher, decorator_name, &block)
-
8
clear_caches_calculated_from_columns
-
8
decorator_name = decorator_name.to_s
-
-
# Create new hashes so we don't modify parent classes
-
8
self.attribute_type_decorations = attribute_type_decorations.merge(decorator_name => [matcher, block])
-
end
-
-
1
private
-
-
1
def add_user_provided_columns(*)
-
4
super.map do |column|
-
35
decorated_type = attribute_type_decorations.apply(column.name, column.cast_type)
-
35
column.with_type(decorated_type)
-
end
-
end
-
end
-
-
1
class TypeDecorator # :nodoc:
-
1
delegate :clear, to: :@decorations
-
-
1
def initialize(decorations = {})
-
9
@decorations = decorations
-
end
-
-
1
def merge(*args)
-
8
TypeDecorator.new(@decorations.merge(*args))
-
end
-
-
1
def apply(name, type)
-
35
decorations = decorators_for(name, type)
-
35
decorations.inject(type) do |new_type, block|
-
6
block.call(new_type)
-
end
-
end
-
-
1
private
-
-
1
def decorators_for(name, type)
-
35
matching(name, type).map(&:last)
-
end
-
-
1
def matching(name, type)
-
35
@decorations.values.select do |(matcher, _)|
-
70
matcher.call(name, type)
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module AttributeMethods
-
# = Active Record Attribute Methods Before Type Cast
-
#
-
# <tt>ActiveRecord::AttributeMethods::BeforeTypeCast</tt> provides a way to
-
# read the value of the attributes before typecasting and deserialization.
-
#
-
# class Task < ActiveRecord::Base
-
# end
-
#
-
# task = Task.new(id: '1', completed_on: '2012-10-21')
-
# task.id # => 1
-
# task.completed_on # => Sun, 21 Oct 2012
-
#
-
# task.attributes_before_type_cast
-
# # => {"id"=>"1", "completed_on"=>"2012-10-21", ... }
-
# task.read_attribute_before_type_cast('id') # => "1"
-
# task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
-
#
-
# In addition to #read_attribute_before_type_cast and #attributes_before_type_cast,
-
# it declares a method for all attributes with the <tt>*_before_type_cast</tt>
-
# suffix.
-
#
-
# task.id_before_type_cast # => "1"
-
# task.completed_on_before_type_cast # => "2012-10-21"
-
1
module BeforeTypeCast
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
attribute_method_suffix "_before_type_cast"
-
1
attribute_method_suffix "_came_from_user?"
-
end
-
-
# Returns the value of the attribute identified by +attr_name+ before
-
# typecasting and deserialization.
-
#
-
# class Task < ActiveRecord::Base
-
# end
-
#
-
# task = Task.new(id: '1', completed_on: '2012-10-21')
-
# task.read_attribute('id') # => 1
-
# task.read_attribute_before_type_cast('id') # => '1'
-
# task.read_attribute('completed_on') # => Sun, 21 Oct 2012
-
# task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
-
# task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
-
1
def read_attribute_before_type_cast(attr_name)
-
702
@attributes[attr_name.to_s].value_before_type_cast
-
end
-
-
# Returns a hash of attributes before typecasting and deserialization.
-
#
-
# class Task < ActiveRecord::Base
-
# end
-
#
-
# task = Task.new(title: nil, is_done: true, completed_on: '2012-10-21')
-
# task.attributes
-
# # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>Sun, 21 Oct 2012, "created_at"=>nil, "updated_at"=>nil}
-
# task.attributes_before_type_cast
-
# # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil}
-
1
def attributes_before_type_cast
-
@attributes.values_before_type_cast
-
end
-
-
1
private
-
-
# Handle *_before_type_cast for method_missing.
-
1
def attribute_before_type_cast(attribute_name)
-
read_attribute_before_type_cast(attribute_name)
-
end
-
-
1
def attribute_came_from_user?(attribute_name)
-
@attributes[attribute_name].came_from_user?
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/module/attribute_accessors'
-
-
1
module ActiveRecord
-
1
module AttributeMethods
-
1
module Dirty # :nodoc:
-
1
extend ActiveSupport::Concern
-
-
1
include ActiveModel::Dirty
-
-
1
included do
-
1
if self < ::ActiveRecord::Timestamp
-
raise "You cannot include Dirty after Timestamp"
-
end
-
-
1
class_attribute :partial_writes, instance_writer: false
-
1
self.partial_writes = true
-
end
-
-
# Attempts to +save+ the record and clears changed attributes if successful.
-
1
def save(*)
-
18
if status = super
-
14
changes_applied
-
end
-
18
status
-
end
-
-
# Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
-
1
def save!(*)
-
17
super.tap do
-
17
changes_applied
-
end
-
end
-
-
# <tt>reload</tt> the record and clears changed attributes.
-
1
def reload(*)
-
super.tap do
-
clear_changes_information
-
end
-
end
-
-
1
def initialize_dup(other) # :nodoc:
-
super
-
calculate_changes_from_defaults
-
end
-
-
1
def changes_applied
-
31
super
-
31
store_original_raw_attributes
-
end
-
-
1
def clear_changes_information
-
super
-
original_raw_attributes.clear
-
end
-
-
1
def changed_attributes
-
# This should only be set by methods which will call changed_attributes
-
# multiple times when it is known that the computed value cannot change.
-
485
if defined?(@cached_changed_attributes)
-
414
@cached_changed_attributes
-
else
-
71
super.reverse_merge(attributes_changed_in_place).freeze
-
end
-
end
-
-
1
def changes
-
31
cache_changed_attributes do
-
31
super
-
end
-
end
-
-
1
def attribute_changed_in_place?(attr_name)
-
1547
old_value = original_raw_attribute(attr_name)
-
1547
@attributes[attr_name].changed_in_place_from?(old_value)
-
end
-
-
1
private
-
-
1
def changes_include?(attr_name)
-
907
super || attribute_changed_in_place?(attr_name)
-
end
-
-
1
def calculate_changes_from_defaults
-
@changed_attributes = nil
-
self.class.column_defaults.each do |attr, orig_value|
-
set_attribute_was(attr, orig_value) if _field_changed?(attr, orig_value)
-
end
-
end
-
-
# Wrap write_attribute to remember original attribute value.
-
1
def write_attribute(attr, value)
-
520
attr = attr.to_s
-
-
520
old_value = old_attribute_value(attr)
-
-
520
result = super
-
520
store_original_raw_attribute(attr)
-
520
save_changed_attribute(attr, old_value)
-
520
result
-
end
-
-
1
def raw_write_attribute(attr, value)
-
attr = attr.to_s
-
-
result = super
-
original_raw_attributes[attr] = value
-
result
-
end
-
-
1
def save_changed_attribute(attr, old_value)
-
520
clear_changed_attributes_cache
-
520
if attribute_changed_by_setter?(attr)
-
6
clear_attribute_changes(attr) unless _field_changed?(attr, old_value)
-
else
-
514
set_attribute_was(attr, old_value) if _field_changed?(attr, old_value)
-
end
-
end
-
-
1
def old_attribute_value(attr)
-
520
if attribute_changed?(attr)
-
6
changed_attributes[attr]
-
else
-
514
clone_attribute_value(:_read_attribute, attr)
-
end
-
end
-
-
1
def _update_record(*)
-
3
partial_writes? ? super(keys_for_partial_write) : super
-
end
-
-
1
def _create_record(*)
-
28
partial_writes? ? super(keys_for_partial_write) : super
-
end
-
-
# Serialized attributes should always be written in case they've been
-
# changed in place.
-
1
def keys_for_partial_write
-
31
changed & persistable_attribute_names
-
end
-
-
1
def _field_changed?(attr, old_value)
-
520
@attributes[attr].changed_from?(old_value)
-
end
-
-
1
def attributes_changed_in_place
-
71
changed_in_place.each_with_object({}) do |attr_name, h|
-
orig = @attributes[attr_name].original_value
-
h[attr_name] = orig
-
end
-
end
-
-
1
def changed_in_place
-
71
self.class.attribute_names.select do |attr_name|
-
1029
attribute_changed_in_place?(attr_name)
-
end
-
end
-
-
1
def original_raw_attribute(attr_name)
-
1547
original_raw_attributes.fetch(attr_name) do
-
702
read_attribute_before_type_cast(attr_name)
-
end
-
end
-
-
1
def original_raw_attributes
-
2515
@original_raw_attributes ||= {}
-
end
-
-
1
def store_original_raw_attribute(attr_name)
-
968
original_raw_attributes[attr_name] = @attributes[attr_name].value_for_database rescue nil
-
end
-
-
1
def store_original_raw_attributes
-
31
attribute_names.each do |attr|
-
448
store_original_raw_attribute(attr)
-
end
-
end
-
-
1
def cache_changed_attributes
-
31
@cached_changed_attributes = changed_attributes
-
31
yield
-
ensure
-
31
clear_changed_attributes_cache
-
end
-
-
1
def clear_changed_attributes_cache
-
551
remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes)
-
end
-
end
-
end
-
end
-
1
require 'set'
-
-
1
module ActiveRecord
-
1
module AttributeMethods
-
1
module PrimaryKey
-
1
extend ActiveSupport::Concern
-
-
# Returns this record's primary key value wrapped in an Array if one is
-
# available.
-
1
def to_key
-
sync_with_transaction_state
-
key = self.id
-
[key] if key
-
end
-
-
# Returns the primary key value.
-
1
def id
-
if pk = self.class.primary_key
-
sync_with_transaction_state
-
_read_attribute(pk)
-
end
-
end
-
-
# Sets the primary key value.
-
1
def id=(value)
-
sync_with_transaction_state
-
write_attribute(self.class.primary_key, value) if self.class.primary_key
-
end
-
-
# Queries the primary key value.
-
1
def id?
-
sync_with_transaction_state
-
query_attribute(self.class.primary_key)
-
end
-
-
# Returns the primary key value before type cast.
-
1
def id_before_type_cast
-
sync_with_transaction_state
-
read_attribute_before_type_cast(self.class.primary_key)
-
end
-
-
# Returns the primary key previous value.
-
1
def id_was
-
sync_with_transaction_state
-
attribute_was(self.class.primary_key)
-
end
-
-
1
protected
-
-
1
def attribute_method?(attr_name)
-
6
attr_name == 'id' || super
-
end
-
-
1
module ClassMethods
-
1
def define_method_attribute(attr_name)
-
35
super
-
-
35
if attr_name == primary_key && attr_name != 'id'
-
generated_attribute_methods.send(:alias_method, :id, primary_key)
-
end
-
end
-
-
1
ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was).to_set
-
-
1
def dangerous_attribute_method?(method_name)
-
387
super && !ID_ATTRIBUTE_METHODS.include?(method_name)
-
end
-
-
# Defines the primary key field -- can be overridden in subclasses.
-
# Overwriting will negate any effect of the +primary_key_prefix_type+
-
# setting, though.
-
1
def primary_key
-
908
@primary_key = reset_primary_key unless defined? @primary_key
-
908
@primary_key
-
end
-
-
# Returns a quoted version of the primary key name, used to construct
-
# SQL statements.
-
1
def quoted_primary_key
-
@quoted_primary_key ||= connection.quote_column_name(primary_key)
-
end
-
-
1
def reset_primary_key #:nodoc:
-
3
if self == base_class
-
3
self.primary_key = get_primary_key(base_class.name)
-
else
-
self.primary_key = base_class.primary_key
-
end
-
end
-
-
1
def get_primary_key(base_name) #:nodoc:
-
3
if base_name && primary_key_prefix_type == :table_name
-
base_name.foreign_key(false)
-
3
elsif base_name && primary_key_prefix_type == :table_name_with_underscore
-
base_name.foreign_key
-
else
-
3
if ActiveRecord::Base != self && table_exists?
-
3
connection.schema_cache.primary_keys(table_name)
-
else
-
'id'
-
end
-
end
-
end
-
-
# Sets the name of the primary key column.
-
#
-
# class Project < ActiveRecord::Base
-
# self.primary_key = 'sysid'
-
# end
-
#
-
# You can also define the +primary_key+ method yourself:
-
#
-
# class Project < ActiveRecord::Base
-
# def self.primary_key
-
# 'foo_' + super
-
# end
-
# end
-
#
-
# Project.primary_key # => "foo_id"
-
1
def primary_key=(value)
-
3
@primary_key = value && value.to_s
-
3
@quoted_primary_key = nil
-
3
@attributes_builder = nil
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module AttributeMethods
-
1
module Query
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
attribute_method_suffix "?"
-
end
-
-
1
def query_attribute(attr_name)
-
value = self[attr_name]
-
-
case value
-
when true then true
-
when false, nil then false
-
else
-
column = self.class.columns_hash[attr_name]
-
if column.nil?
-
if Numeric === value || value !~ /[^0-9]/
-
!value.to_i.zero?
-
else
-
return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value)
-
!value.blank?
-
end
-
elsif column.number?
-
!value.zero?
-
else
-
!value.blank?
-
end
-
end
-
end
-
-
1
private
-
# Handle *? for method_missing.
-
1
def attribute?(attribute_name)
-
query_attribute(attribute_name)
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/module/method_transplanting'
-
-
1
module ActiveRecord
-
1
module AttributeMethods
-
1
module Read
-
1
ReaderMethodCache = Class.new(AttributeMethodCache) {
-
1
private
-
# We want to generate the methods via module_eval rather than
-
# define_method, because define_method is slower on dispatch.
-
# Evaluating many similar methods may use more memory as the instruction
-
# sequences are duplicated and cached (in MRI). define_method may
-
# be slower on dispatch, but if you're careful about the closure
-
# created, then define_method will consume much less memory.
-
#
-
# But sometimes the database might return columns with
-
# characters that are not allowed in normal method names (like
-
# 'my_column(omg)'. So to work around this we first define with
-
# the __temp__ identifier, and then use alias method to rename
-
# it to what we want.
-
#
-
# We are also defining a constant to hold the frozen string of
-
# the attribute name. Using a constant means that we do not have
-
# to allocate an object on each call to the attribute method.
-
# Making it frozen means that it doesn't get duped when used to
-
# key the @attributes in read_attribute.
-
1
def method_body(method_name, const_name)
-
<<-EOMETHOD
-
27
def #{method_name}
-
name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name}
-
_read_attribute(name) { |n| missing_attribute(n, caller) }
-
end
-
EOMETHOD
-
end
-
}.new
-
-
1
extend ActiveSupport::Concern
-
-
1
module ClassMethods
-
1
[:cache_attributes, :cached_attributes, :cache_attribute?].each do |method_name|
-
3
define_method method_name do |*|
-
cached_attributes_deprecation_warning(method_name)
-
true
-
end
-
end
-
-
1
protected
-
-
1
def cached_attributes_deprecation_warning(method_name)
-
ActiveSupport::Deprecation.warn "Calling `#{method_name}` is no longer necessary. All attributes are cached."
-
end
-
-
1
if Module.methods_transplantable?
-
1
def define_method_attribute(name)
-
35
method = ReaderMethodCache[name]
-
70
generated_attribute_methods.module_eval { define_method name, method }
-
end
-
else
-
def define_method_attribute(name)
-
safe_name = name.unpack('h*').first
-
temp_method = "__temp__#{safe_name}"
-
-
ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
-
-
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
-
def #{temp_method}
-
name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
-
_read_attribute(name) { |n| missing_attribute(n, caller) }
-
end
-
STR
-
-
generated_attribute_methods.module_eval do
-
alias_method name, temp_method
-
undef_method temp_method
-
end
-
end
-
end
-
end
-
-
1
ID = 'id'.freeze
-
-
# Returns the value of the attribute identified by <tt>attr_name</tt> after
-
# it has been typecast (for example, "2004-12-12" in a date column is cast
-
# to a date object, like Date.new(2004, 12, 12)).
-
1
def read_attribute(attr_name, &block)
-
2
name = attr_name.to_s
-
2
name = self.class.primary_key if name == ID
-
2
_read_attribute(name, &block)
-
end
-
-
# This method exists to avoid the expensive primary_key check internally, without
-
# breaking compatibility with the read_attribute API
-
1
def _read_attribute(attr_name) # :nodoc:
-
1864
@attributes.fetch_value(attr_name.to_s) { |n| yield n if block_given? }
-
end
-
-
1
private
-
-
1
def attribute(attribute_name)
-
_read_attribute(attribute_name)
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/string/filters'
-
-
1
module ActiveRecord
-
1
module AttributeMethods
-
1
module Serialization
-
1
extend ActiveSupport::Concern
-
-
1
module ClassMethods
-
# If you have an attribute that needs to be saved to the database as an
-
# object, and retrieved as the same object, then specify the name of that
-
# attribute using this method and it will be handled automatically. The
-
# serialization is done through YAML. If +class_name+ is specified, the
-
# serialized object must be of that class on assignment and retrieval.
-
# Otherwise <tt>SerializationTypeMismatch</tt> will be raised.
-
#
-
# ==== Parameters
-
#
-
# * +attr_name+ - The field name that should be serialized.
-
# * +class_name_or_coder+ - Optional, a coder object, which responds to `.load` / `.dump`
-
# or a class name that the object type should be equal to.
-
#
-
# ==== Example
-
#
-
# # Serialize a preferences attribute.
-
# class User < ActiveRecord::Base
-
# serialize :preferences
-
# end
-
#
-
# # Serialize preferences using JSON as coder.
-
# class User < ActiveRecord::Base
-
# serialize :preferences, JSON
-
# end
-
#
-
# # Serialize preferences as Hash using YAML coder.
-
# class User < ActiveRecord::Base
-
# serialize :preferences, Hash
-
# end
-
1
def serialize(attr_name, class_name_or_coder = Object)
-
# When ::JSON is used, force it to go through the Active Support JSON encoder
-
# to ensure special objects (e.g. Active Record models) are dumped correctly
-
# using the #as_json hook.
-
coder = if class_name_or_coder == ::JSON
-
Coders::JSON
-
elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
-
class_name_or_coder
-
else
-
Coders::YAMLColumn.new(class_name_or_coder)
-
end
-
-
decorate_attribute_type(attr_name, :serialize) do |type|
-
Type::Serialized.new(type, coder)
-
end
-
end
-
-
1
def serialized_attributes
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
`serialized_attributes` is deprecated without replacement, and will
-
be removed in Rails 5.0.
-
MSG
-
-
@serialized_attributes ||= Hash[
-
columns.select { |t| t.cast_type.is_a?(Type::Serialized) }.map { |c|
-
[c.name, c.cast_type.coder]
-
}
-
]
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module AttributeMethods
-
1
module TimeZoneConversion
-
1
class TimeZoneConverter < DelegateClass(Type::Value) # :nodoc:
-
1
include Type::Decorator
-
-
1
def type_cast_from_database(value)
-
62
convert_time_to_time_zone(super)
-
end
-
-
1
def type_cast_from_user(value)
-
58
if value.is_a?(Array)
-
value.map { |v| type_cast_from_user(v) }
-
58
elsif value.respond_to?(:in_time_zone)
-
58
begin
-
58
value.in_time_zone || super
-
rescue ArgumentError
-
nil
-
end
-
end
-
end
-
-
1
def convert_time_to_time_zone(value)
-
62
if value.is_a?(Array)
-
value.map { |v| convert_time_to_time_zone(v) }
-
62
elsif value.acts_like?(:time)
-
6
value.in_time_zone
-
else
-
56
value
-
end
-
end
-
end
-
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
mattr_accessor :time_zone_aware_attributes, instance_writer: false
-
1
self.time_zone_aware_attributes = false
-
-
1
class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false
-
1
self.skip_time_zone_conversion_for_attributes = []
-
end
-
-
1
module ClassMethods
-
1
private
-
-
1
def inherited(subclass)
-
# We need to apply this decorator here, rather than on module inclusion. The closure
-
# created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the
-
# sub class being decorated. As such, changes to `time_zone_aware_attributes`, or
-
# `skip_time_zone_conversion_for_attributes` would not be picked up.
-
4
subclass.class_eval do
-
39
matcher = ->(name, type) { create_time_zone_conversion_attribute?(name, type) }
-
4
decorate_matching_attribute_types(matcher, :_time_zone_conversion) do |type|
-
6
TimeZoneConverter.new(type)
-
end
-
end
-
4
super
-
end
-
-
1
def create_time_zone_conversion_attribute?(name, cast_type)
-
time_zone_aware_attributes &&
-
35
!self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) &&
-
35
(:datetime == cast_type.type)
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/module/method_transplanting'
-
-
1
module ActiveRecord
-
1
module AttributeMethods
-
1
module Write
-
1
WriterMethodCache = Class.new(AttributeMethodCache) {
-
1
private
-
-
1
def method_body(method_name, const_name)
-
<<-EOMETHOD
-
27
def #{method_name}(value)
-
name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name}
-
write_attribute(name, value)
-
end
-
EOMETHOD
-
end
-
}.new
-
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
attribute_method_suffix "="
-
end
-
-
1
module ClassMethods
-
1
protected
-
-
1
if Module.methods_transplantable?
-
1
def define_method_attribute=(name)
-
35
method = WriterMethodCache[name]
-
35
generated_attribute_methods.module_eval {
-
35
define_method "#{name}=", method
-
}
-
end
-
else
-
def define_method_attribute=(name)
-
safe_name = name.unpack('h*').first
-
ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
-
-
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
-
def __temp__#{safe_name}=(value)
-
name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
-
write_attribute(name, value)
-
end
-
alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
-
undef_method :__temp__#{safe_name}=
-
STR
-
end
-
end
-
end
-
-
# Updates the attribute identified by <tt>attr_name</tt> with the
-
# specified +value+. Empty strings for fixnum and float columns are
-
# turned into +nil+.
-
1
def write_attribute(attr_name, value)
-
520
write_attribute_with_type_cast(attr_name, value, true)
-
end
-
-
1
def raw_write_attribute(attr_name, value)
-
write_attribute_with_type_cast(attr_name, value, false)
-
end
-
-
1
private
-
# Handle *= for method_missing.
-
1
def attribute=(attribute_name, value)
-
write_attribute(attribute_name, value)
-
end
-
-
1
def write_attribute_with_type_cast(attr_name, value, should_type_cast)
-
520
attr_name = attr_name.to_s
-
520
attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
-
-
520
if should_type_cast
-
520
@attributes.write_from_user(attr_name, value)
-
else
-
@attributes.write_cast_value(attr_name, value)
-
end
-
-
520
value
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Attributes # :nodoc:
-
1
extend ActiveSupport::Concern
-
-
1
Type = ActiveRecord::Type
-
-
1
included do
-
1
class_attribute :user_provided_columns, instance_accessor: false # :internal:
-
1
class_attribute :user_provided_defaults, instance_accessor: false # :internal:
-
1
self.user_provided_columns = {}
-
1
self.user_provided_defaults = {}
-
-
1
delegate :persistable_attribute_names, to: :class
-
end
-
-
1
module ClassMethods # :nodoc:
-
# Defines or overrides a attribute on this model. This allows customization of
-
# Active Record's type casting behavior, as well as adding support for user defined
-
# types.
-
#
-
# +name+ The name of the methods to define attribute methods for, and the column which
-
# this will persist to.
-
#
-
# +cast_type+ A type object that contains information about how to type cast the value.
-
# See the examples section for more information.
-
#
-
# ==== Options
-
# The options hash accepts the following options:
-
#
-
# +default+ is the default value that the column should use on a new record.
-
#
-
# ==== Examples
-
#
-
# The type detected by Active Record can be overridden.
-
#
-
# # db/schema.rb
-
# create_table :store_listings, force: true do |t|
-
# t.decimal :price_in_cents
-
# end
-
#
-
# # app/models/store_listing.rb
-
# class StoreListing < ActiveRecord::Base
-
# end
-
#
-
# store_listing = StoreListing.new(price_in_cents: '10.1')
-
#
-
# # before
-
# store_listing.price_in_cents # => BigDecimal.new(10.1)
-
#
-
# class StoreListing < ActiveRecord::Base
-
# attribute :price_in_cents, Type::Integer.new
-
# end
-
#
-
# # after
-
# store_listing.price_in_cents # => 10
-
#
-
# Users may also define their own custom types, as long as they respond to the methods
-
# defined on the value type. The `type_cast` method on your type object will be called
-
# with values both from the database, and from your controllers. See
-
# `ActiveRecord::Attributes::Type::Value` for the expected API. It is recommended that your
-
# type objects inherit from an existing type, or the base value type.
-
#
-
# class MoneyType < ActiveRecord::Type::Integer
-
# def type_cast(value)
-
# if value.include?('$')
-
# price_in_dollars = value.gsub(/\$/, '').to_f
-
# price_in_dollars * 100
-
# else
-
# value.to_i
-
# end
-
# end
-
# end
-
#
-
# class StoreListing < ActiveRecord::Base
-
# attribute :price_in_cents, MoneyType.new
-
# end
-
#
-
# store_listing = StoreListing.new(price_in_cents: '$10.00')
-
# store_listing.price_in_cents # => 1000
-
1
def attribute(name, cast_type, options = {})
-
name = name.to_s
-
clear_caches_calculated_from_columns
-
# Assign a new hash to ensure that subclasses do not share a hash
-
self.user_provided_columns = user_provided_columns.merge(name => cast_type)
-
-
if options.key?(:default)
-
self.user_provided_defaults = user_provided_defaults.merge(name => options[:default])
-
end
-
end
-
-
# Returns an array of column objects for the table associated with this class.
-
1
def columns
-
8
@columns ||= add_user_provided_columns(connection.schema_cache.columns(table_name))
-
end
-
-
# Returns a hash of column objects for the table associated with this class.
-
1
def columns_hash
-
813
@columns_hash ||= Hash[columns.map { |c| [c.name, c] }]
-
end
-
-
1
def persistable_attribute_names # :nodoc:
-
31
@persistable_attribute_names ||= connection.schema_cache.columns_hash(table_name).keys
-
end
-
-
1
def reset_column_information # :nodoc:
-
super
-
clear_caches_calculated_from_columns
-
end
-
-
1
private
-
-
1
def add_user_provided_columns(schema_columns)
-
4
existing_columns = schema_columns.map do |column|
-
35
new_type = user_provided_columns[column.name]
-
35
if new_type
-
column.with_type(new_type)
-
else
-
35
column
-
end
-
end
-
-
4
existing_column_names = existing_columns.map(&:name)
-
4
new_columns = user_provided_columns.except(*existing_column_names).map do |(name, type)|
-
connection.new_column(name, nil, type)
-
end
-
-
4
existing_columns + new_columns
-
end
-
-
1
def clear_caches_calculated_from_columns
-
12
@attributes_builder = nil
-
12
@column_names = nil
-
12
@column_types = nil
-
12
@columns = nil
-
12
@columns_hash = nil
-
12
@content_columns = nil
-
12
@default_attributes = nil
-
12
@persistable_attribute_names = nil
-
end
-
-
1
def raw_default_values
-
3
super.merge(user_provided_defaults)
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
# = Active Record Autosave Association
-
#
-
# +AutosaveAssociation+ is a module that takes care of automatically saving
-
# associated records when their parent is saved. In addition to saving, it
-
# also destroys any associated records that were marked for destruction.
-
# (See +mark_for_destruction+ and <tt>marked_for_destruction?</tt>).
-
#
-
# Saving of the parent, its associations, and the destruction of marked
-
# associations, all happen inside a transaction. This should never leave the
-
# database in an inconsistent state.
-
#
-
# If validations for any of the associations fail, their error messages will
-
# be applied to the parent.
-
#
-
# Note that it also means that associations marked for destruction won't
-
# be destroyed directly. They will however still be marked for destruction.
-
#
-
# Note that <tt>autosave: false</tt> is not same as not declaring <tt>:autosave</tt>.
-
# When the <tt>:autosave</tt> option is not present then new association records are
-
# saved but the updated association records are not saved.
-
#
-
# == Validation
-
#
-
# Children records are validated unless <tt>:validate</tt> is +false+.
-
#
-
# == Callbacks
-
#
-
# Association with autosave option defines several callbacks on your
-
# model (before_save, after_create, after_update). Please note that
-
# callbacks are executed in the order they were defined in
-
# model. You should avoid modifying the association content, before
-
# autosave callbacks are executed. Placing your callbacks after
-
# associations is usually a good practice.
-
#
-
# === One-to-one Example
-
#
-
# class Post < ActiveRecord::Base
-
# has_one :author, autosave: true
-
# end
-
#
-
# Saving changes to the parent and its associated model can now be performed
-
# automatically _and_ atomically:
-
#
-
# post = Post.find(1)
-
# post.title # => "The current global position of migrating ducks"
-
# post.author.name # => "alloy"
-
#
-
# post.title = "On the migration of ducks"
-
# post.author.name = "Eloy Duran"
-
#
-
# post.save
-
# post.reload
-
# post.title # => "On the migration of ducks"
-
# post.author.name # => "Eloy Duran"
-
#
-
# Destroying an associated model, as part of the parent's save action, is as
-
# simple as marking it for destruction:
-
#
-
# post.author.mark_for_destruction
-
# post.author.marked_for_destruction? # => true
-
#
-
# Note that the model is _not_ yet removed from the database:
-
#
-
# id = post.author.id
-
# Author.find_by(id: id).nil? # => false
-
#
-
# post.save
-
# post.reload.author # => nil
-
#
-
# Now it _is_ removed from the database:
-
#
-
# Author.find_by(id: id).nil? # => true
-
#
-
# === One-to-many Example
-
#
-
# When <tt>:autosave</tt> is not declared new children are saved when their parent is saved:
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :comments # :autosave option is not declared
-
# end
-
#
-
# post = Post.new(title: 'ruby rocks')
-
# post.comments.build(body: 'hello world')
-
# post.save # => saves both post and comment
-
#
-
# post = Post.create(title: 'ruby rocks')
-
# post.comments.build(body: 'hello world')
-
# post.save # => saves both post and comment
-
#
-
# post = Post.create(title: 'ruby rocks')
-
# post.comments.create(body: 'hello world')
-
# post.save # => saves both post and comment
-
#
-
# When <tt>:autosave</tt> is true all children are saved, no matter whether they
-
# are new records or not:
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :comments, autosave: true
-
# end
-
#
-
# post = Post.create(title: 'ruby rocks')
-
# post.comments.create(body: 'hello world')
-
# post.comments[0].body = 'hi everyone'
-
# post.comments.build(body: "good morning.")
-
# post.title += "!"
-
# post.save # => saves both post and comments.
-
#
-
# Destroying one of the associated models as part of the parent's save action
-
# is as simple as marking it for destruction:
-
#
-
# post.comments # => [#<Comment id: 1, ...>, #<Comment id: 2, ...]>
-
# post.comments[1].mark_for_destruction
-
# post.comments[1].marked_for_destruction? # => true
-
# post.comments.length # => 2
-
#
-
# Note that the model is _not_ yet removed from the database:
-
#
-
# id = post.comments.last.id
-
# Comment.find_by(id: id).nil? # => false
-
#
-
# post.save
-
# post.reload.comments.length # => 1
-
#
-
# Now it _is_ removed from the database:
-
#
-
# Comment.find_by(id: id).nil? # => true
-
-
1
module AutosaveAssociation
-
1
extend ActiveSupport::Concern
-
-
1
module AssociationBuilderExtension #:nodoc:
-
1
def self.build(model, reflection)
-
2
model.send(:add_autosave_association_callbacks, reflection)
-
end
-
-
1
def self.valid_options
-
2
[ :autosave ]
-
end
-
end
-
-
1
included do
-
1
Associations::Builder::Association.extensions << AssociationBuilderExtension
-
end
-
-
1
module ClassMethods
-
1
private
-
-
1
def define_non_cyclic_method(name, &block)
-
3
return if method_defined?(name)
-
3
define_method(name) do |*args|
-
60
result = true; @_already_called ||= {}
-
# Loop prevention for validation of associations
-
60
unless @_already_called[name]
-
60
begin
-
60
@_already_called[name]=true
-
60
result = instance_eval(&block)
-
ensure
-
60
@_already_called[name]=false
-
end
-
end
-
-
60
result
-
end
-
end
-
-
# Adds validation and save callbacks for the association as specified by
-
# the +reflection+.
-
#
-
# For performance reasons, we don't check whether to validate at runtime.
-
# However the validation and callback methods are lazy and those methods
-
# get created when they are invoked for the very first time. However,
-
# this can change, for instance, when using nested attributes, which is
-
# called _after_ the association has been defined. Since we don't want
-
# the callbacks to get defined multiple times, there are guards that
-
# check if the save or validation methods have already been defined
-
# before actually defining them.
-
1
def add_autosave_association_callbacks(reflection)
-
2
save_method = :"autosave_associated_records_for_#{reflection.name}"
-
-
2
if reflection.collection?
-
1
before_save :before_save_collection_association
-
-
24
define_non_cyclic_method(save_method) { save_collection_association(reflection) }
-
# Doesn't use after_save as that would save associations added in after_create/after_update twice
-
1
after_create save_method
-
1
after_update save_method
-
elsif reflection.has_one?
-
define_method(save_method) { save_has_one_association(reflection) } unless method_defined?(save_method)
-
# Configures two callbacks instead of a single after_save so that
-
# the model may rely on their execution order relative to its
-
# own callbacks.
-
#
-
# For example, given that after_creates run before after_saves, if
-
# we configured instead an after_save there would be no way to fire
-
# a custom after_create callback after the child association gets
-
# created.
-
after_create save_method
-
after_update save_method
-
else
-
2
define_non_cyclic_method(save_method) { save_belongs_to_association(reflection) }
-
1
before_save save_method
-
end
-
-
2
define_autosave_validation_callbacks(reflection)
-
end
-
-
1
def define_autosave_validation_callbacks(reflection)
-
2
validation_method = :"validate_associated_records_for_#{reflection.name}"
-
2
if reflection.validate? && !method_defined?(validation_method)
-
1
if reflection.collection?
-
1
method = :validate_collection_association
-
else
-
method = :validate_single_association
-
end
-
-
37
define_non_cyclic_method(validation_method) { send(method, reflection) }
-
1
validate validation_method
-
end
-
end
-
end
-
-
# Reloads the attributes of the object as usual and clears <tt>marked_for_destruction</tt> flag.
-
1
def reload(options = nil)
-
@marked_for_destruction = false
-
@destroyed_by_association = nil
-
super
-
end
-
-
# Marks this record to be destroyed as part of the parents save transaction.
-
# This does _not_ actually destroy the record instantly, rather child record will be destroyed
-
# when <tt>parent.save</tt> is called.
-
#
-
# Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
-
1
def mark_for_destruction
-
@marked_for_destruction = true
-
end
-
-
# Returns whether or not this record will be destroyed as part of the parents save transaction.
-
#
-
# Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
-
1
def marked_for_destruction?
-
@marked_for_destruction
-
end
-
-
# Records the association that is being destroyed and destroying this
-
# record in the process.
-
1
def destroyed_by_association=(reflection)
-
@destroyed_by_association = reflection
-
end
-
-
# Returns the association for the parent being destroyed.
-
#
-
# Used to avoid updating the counter cache unnecessarily.
-
1
def destroyed_by_association
-
@destroyed_by_association
-
end
-
-
# Returns whether or not this record has been changed in any way (including whether
-
# any of its nested autosave associations are likewise changed)
-
1
def changed_for_autosave?
-
new_record? || changed? || marked_for_destruction? || nested_records_changed_for_autosave?
-
end
-
-
1
private
-
-
# Returns the record for an association collection that should be validated
-
# or saved. If +autosave+ is +false+ only new records will be returned,
-
# unless the parent is/was a new record itself.
-
1
def associated_records_to_validate_or_save(association, new_record, autosave)
-
if new_record
-
association && association.target
-
elsif autosave
-
association.target.find_all { |record| record.changed_for_autosave? }
-
else
-
association.target.find_all { |record| record.new_record? }
-
end
-
end
-
-
# go through nested autosave associations that are loaded in memory (without loading
-
# any new ones), and return true if is changed for autosave
-
1
def nested_records_changed_for_autosave?
-
@_nested_records_changed_for_autosave_already_called ||= false
-
return false if @_nested_records_changed_for_autosave_already_called
-
begin
-
@_nested_records_changed_for_autosave_already_called = true
-
self.class._reflections.values.any? do |reflection|
-
if reflection.options[:autosave]
-
association = association_instance_get(reflection.name)
-
association && Array.wrap(association.target).any?(&:changed_for_autosave?)
-
end
-
end
-
ensure
-
@_nested_records_changed_for_autosave_already_called = false
-
end
-
end
-
-
# Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
-
# turned on for the association.
-
1
def validate_single_association(reflection)
-
association = association_instance_get(reflection.name)
-
record = association && association.reader
-
association_valid?(reflection, record) if record
-
end
-
-
# Validate the associated records if <tt>:validate</tt> or
-
# <tt>:autosave</tt> is turned on for the association specified by
-
# +reflection+.
-
1
def validate_collection_association(reflection)
-
36
if association = association_instance_get(reflection.name)
-
if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
-
records.each { |record| association_valid?(reflection, record) }
-
end
-
end
-
end
-
-
# Returns whether or not the association is valid and applies any errors to
-
# the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
-
# enabled records if they're marked_for_destruction? or destroyed.
-
1
def association_valid?(reflection, record)
-
return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
-
-
validation_context = self.validation_context unless [:create, :update].include?(self.validation_context)
-
unless valid = record.valid?(validation_context)
-
if reflection.options[:autosave]
-
record.errors.each do |attribute, message|
-
attribute = "#{reflection.name}.#{attribute}"
-
errors[attribute] << message
-
errors[attribute].uniq!
-
end
-
else
-
errors.add(reflection.name)
-
end
-
end
-
valid
-
end
-
-
# Is used as a before_save callback to check while saving a collection
-
# association whether or not the parent was a new record before saving.
-
1
def before_save_collection_association
-
23
@new_record_before_save = new_record?
-
23
true
-
end
-
-
# Saves any new associated records, or all loaded autosave associations if
-
# <tt>:autosave</tt> is enabled on the association.
-
#
-
# In addition, it destroys all children that were marked for destruction
-
# with mark_for_destruction.
-
#
-
# This all happens inside a transaction, _if_ the Transactions module is included into
-
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
-
1
def save_collection_association(reflection)
-
23
if association = association_instance_get(reflection.name)
-
autosave = reflection.options[:autosave]
-
-
if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
-
if autosave
-
records_to_destroy = records.select(&:marked_for_destruction?)
-
records_to_destroy.each { |record| association.destroy(record) }
-
records -= records_to_destroy
-
end
-
-
records.each do |record|
-
next if record.destroyed?
-
-
saved = true
-
-
if autosave != false && (@new_record_before_save || record.new_record?)
-
if autosave
-
saved = association.insert_record(record, false)
-
else
-
association.insert_record(record) unless reflection.nested?
-
end
-
elsif autosave
-
saved = record.save(:validate => false)
-
end
-
-
raise ActiveRecord::Rollback unless saved
-
end
-
end
-
-
# reconstruct the scope now that we know the owner's id
-
association.reset_scope if association.respond_to?(:reset_scope)
-
end
-
end
-
-
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled
-
# on the association.
-
#
-
# In addition, it will destroy the association if it was marked for
-
# destruction with mark_for_destruction.
-
#
-
# This all happens inside a transaction, _if_ the Transactions module is included into
-
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
-
1
def save_has_one_association(reflection)
-
association = association_instance_get(reflection.name)
-
record = association && association.load_target
-
-
if record && !record.destroyed?
-
autosave = reflection.options[:autosave]
-
-
if autosave && record.marked_for_destruction?
-
record.destroy
-
elsif autosave != false
-
key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
-
-
if (autosave && record.changed_for_autosave?) || new_record? || record_changed?(reflection, record, key)
-
unless reflection.through_reflection
-
record[reflection.foreign_key] = key
-
end
-
-
saved = record.save(:validate => !autosave)
-
raise ActiveRecord::Rollback if !saved && autosave
-
saved
-
end
-
end
-
end
-
end
-
-
# If the record is new or it has changed, returns true.
-
1
def record_changed?(reflection, record, key)
-
record.new_record? ||
-
(record.has_attribute?(reflection.foreign_key) && record[reflection.foreign_key] != key) ||
-
record.attribute_changed?(reflection.foreign_key)
-
end
-
-
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
-
#
-
# In addition, it will destroy the association if it was marked for destruction.
-
1
def save_belongs_to_association(reflection)
-
1
association = association_instance_get(reflection.name)
-
1
record = association && association.load_target
-
1
if record && !record.destroyed?
-
autosave = reflection.options[:autosave]
-
-
if autosave && record.marked_for_destruction?
-
self[reflection.foreign_key] = nil
-
record.destroy
-
elsif autosave != false
-
saved = record.save(:validate => !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
-
-
if association.updated?
-
association_id = record.send(reflection.options[:primary_key] || :id)
-
self[reflection.foreign_key] = association_id
-
association.loaded!
-
end
-
-
saved if autosave
-
end
-
end
-
end
-
end
-
end
-
1
require 'yaml'
-
1
require 'set'
-
1
require 'active_support/benchmarkable'
-
1
require 'active_support/dependencies'
-
1
require 'active_support/descendants_tracker'
-
1
require 'active_support/time'
-
1
require 'active_support/core_ext/module/attribute_accessors'
-
1
require 'active_support/core_ext/class/delegating_attributes'
-
1
require 'active_support/core_ext/array/extract_options'
-
1
require 'active_support/core_ext/hash/deep_merge'
-
1
require 'active_support/core_ext/hash/slice'
-
1
require 'active_support/core_ext/hash/transform_values'
-
1
require 'active_support/core_ext/string/behavior'
-
1
require 'active_support/core_ext/kernel/singleton_class'
-
1
require 'active_support/core_ext/module/introspection'
-
1
require 'active_support/core_ext/object/duplicable'
-
1
require 'active_support/core_ext/class/subclasses'
-
1
require 'arel'
-
1
require 'active_record/attribute_decorators'
-
1
require 'active_record/errors'
-
1
require 'active_record/log_subscriber'
-
1
require 'active_record/explain_subscriber'
-
1
require 'active_record/relation/delegation'
-
1
require 'active_record/attributes'
-
-
1
module ActiveRecord #:nodoc:
-
# = Active Record
-
#
-
# Active Record objects don't specify their attributes directly, but rather infer them from
-
# the table definition with which they're linked. Adding, removing, and changing attributes
-
# and their type is done directly in the database. Any change is instantly reflected in the
-
# Active Record objects. The mapping that binds a given Active Record class to a certain
-
# database table will happen automatically in most common cases, but can be overwritten for the uncommon ones.
-
#
-
# See the mapping rules in table_name and the full example in link:files/activerecord/README_rdoc.html for more insight.
-
#
-
# == Creation
-
#
-
# Active Records accept constructor parameters either in a hash or as a block. The hash
-
# method is especially useful when you're receiving the data from somewhere else, like an
-
# HTTP request. It works like this:
-
#
-
# user = User.new(name: "David", occupation: "Code Artist")
-
# user.name # => "David"
-
#
-
# You can also use block initialization:
-
#
-
# user = User.new do |u|
-
# u.name = "David"
-
# u.occupation = "Code Artist"
-
# end
-
#
-
# And of course you can just create a bare object and specify the attributes after the fact:
-
#
-
# user = User.new
-
# user.name = "David"
-
# user.occupation = "Code Artist"
-
#
-
# == Conditions
-
#
-
# Conditions can either be specified as a string, array, or hash representing the WHERE-part of an SQL statement.
-
# The array form is to be used when the condition input is tainted and requires sanitization. The string form can
-
# be used for statements that don't involve tainted data. The hash form works much like the array form, except
-
# only equality and range is possible. Examples:
-
#
-
# class User < ActiveRecord::Base
-
# def self.authenticate_unsafely(user_name, password)
-
# where("user_name = '#{user_name}' AND password = '#{password}'").first
-
# end
-
#
-
# def self.authenticate_safely(user_name, password)
-
# where("user_name = ? AND password = ?", user_name, password).first
-
# end
-
#
-
# def self.authenticate_safely_simply(user_name, password)
-
# where(user_name: user_name, password: password).first
-
# end
-
# end
-
#
-
# The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query
-
# and is thus susceptible to SQL-injection attacks if the <tt>user_name</tt> and +password+
-
# parameters come directly from an HTTP request. The <tt>authenticate_safely</tt> and
-
# <tt>authenticate_safely_simply</tt> both will sanitize the <tt>user_name</tt> and +password+
-
# before inserting them in the query, which will ensure that an attacker can't escape the
-
# query and fake the login (or worse).
-
#
-
# When using multiple parameters in the conditions, it can easily become hard to read exactly
-
# what the fourth or fifth question mark is supposed to represent. In those cases, you can
-
# resort to named bind variables instead. That's done by replacing the question marks with
-
# symbols and supplying a hash with values for the matching symbol keys:
-
#
-
# Company.where(
-
# "id = :id AND name = :name AND division = :division AND created_at > :accounting_date",
-
# { id: 3, name: "37signals", division: "First", accounting_date: '2005-01-01' }
-
# ).first
-
#
-
# Similarly, a simple hash without a statement will generate conditions based on equality with the SQL AND
-
# operator. For instance:
-
#
-
# Student.where(first_name: "Harvey", status: 1)
-
# Student.where(params[:student])
-
#
-
# A range may be used in the hash to use the SQL BETWEEN operator:
-
#
-
# Student.where(grade: 9..12)
-
#
-
# An array may be used in the hash to use the SQL IN operator:
-
#
-
# Student.where(grade: [9,11,12])
-
#
-
# When joining tables, nested hashes or keys written in the form 'table_name.column_name'
-
# can be used to qualify the table name of a particular condition. For instance:
-
#
-
# Student.joins(:schools).where(schools: { category: 'public' })
-
# Student.joins(:schools).where('schools.category' => 'public' )
-
#
-
# == Overwriting default accessors
-
#
-
# All column values are automatically available through basic accessors on the Active Record
-
# object, but sometimes you want to specialize this behavior. This can be done by overwriting
-
# the default accessors (using the same name as the attribute) and calling
-
# +super+ to actually change things.
-
#
-
# class Song < ActiveRecord::Base
-
# # Uses an integer of seconds to hold the length of the song
-
#
-
# def length=(minutes)
-
# super(minutes.to_i * 60)
-
# end
-
#
-
# def length
-
# super / 60
-
# end
-
# end
-
#
-
# You can alternatively use <tt>self[:attribute]=(value)</tt> and <tt>self[:attribute]</tt>
-
# or <tt>write_attribute(:attribute, value)</tt> and <tt>read_attribute(:attribute)</tt>.
-
#
-
# == Attribute query methods
-
#
-
# In addition to the basic accessors, query methods are also automatically available on the Active Record object.
-
# Query methods allow you to test whether an attribute value is present.
-
# For numeric values, present is defined as non-zero.
-
#
-
# For example, an Active Record User with the <tt>name</tt> attribute has a <tt>name?</tt> method that you can call
-
# to determine whether the user has a name:
-
#
-
# user = User.new(name: "David")
-
# user.name? # => true
-
#
-
# anonymous = User.new(name: "")
-
# anonymous.name? # => false
-
#
-
# == Accessing attributes before they have been typecasted
-
#
-
# Sometimes you want to be able to read the raw attribute data without having the column-determined
-
# typecast run its course first. That can be done by using the <tt><attribute>_before_type_cast</tt>
-
# accessors that all attributes have. For example, if your Account model has a <tt>balance</tt> attribute,
-
# you can call <tt>account.balance_before_type_cast</tt> or <tt>account.id_before_type_cast</tt>.
-
#
-
# This is especially useful in validation situations where the user might supply a string for an
-
# integer field and you want to display the original string back in an error message. Accessing the
-
# attribute normally would typecast the string to 0, which isn't what you want.
-
#
-
# == Dynamic attribute-based finders
-
#
-
# Dynamic attribute-based finders are a mildly deprecated way of getting (and/or creating) objects
-
# by simple queries without turning to SQL. They work by appending the name of an attribute
-
# to <tt>find_by_</tt> like <tt>Person.find_by_user_name</tt>.
-
# Instead of writing <tt>Person.find_by(user_name: user_name)</tt>, you can use
-
# <tt>Person.find_by_user_name(user_name)</tt>.
-
#
-
# It's possible to add an exclamation point (!) on the end of the dynamic finders to get them to raise an
-
# <tt>ActiveRecord::RecordNotFound</tt> error if they do not return any records,
-
# like <tt>Person.find_by_last_name!</tt>.
-
#
-
# It's also possible to use multiple attributes in the same find by separating them with "_and_".
-
#
-
# Person.find_by(user_name: user_name, password: password)
-
# Person.find_by_user_name_and_password(user_name, password) # with dynamic finder
-
#
-
# It's even possible to call these dynamic finder methods on relations and named scopes.
-
#
-
# Payment.order("created_on").find_by_amount(50)
-
#
-
# == Saving arrays, hashes, and other non-mappable objects in text columns
-
#
-
# Active Record can serialize any object in text columns using YAML. To do so, you must
-
# specify this with a call to the class method +serialize+.
-
# This makes it possible to store arrays, hashes, and other non-mappable objects without doing
-
# any additional work.
-
#
-
# class User < ActiveRecord::Base
-
# serialize :preferences
-
# end
-
#
-
# user = User.create(preferences: { "background" => "black", "display" => large })
-
# User.find(user.id).preferences # => { "background" => "black", "display" => large }
-
#
-
# You can also specify a class option as the second parameter that'll raise an exception
-
# if a serialized object is retrieved as a descendant of a class not in the hierarchy.
-
#
-
# class User < ActiveRecord::Base
-
# serialize :preferences, Hash
-
# end
-
#
-
# user = User.create(preferences: %w( one two three ))
-
# User.find(user.id).preferences # raises SerializationTypeMismatch
-
#
-
# When you specify a class option, the default value for that attribute will be a new
-
# instance of that class.
-
#
-
# class User < ActiveRecord::Base
-
# serialize :preferences, OpenStruct
-
# end
-
#
-
# user = User.new
-
# user.preferences.theme_color = "red"
-
#
-
#
-
# == Single table inheritance
-
#
-
# Active Record allows inheritance by storing the name of the class in a
-
# column that is named "type" by default. See ActiveRecord::Inheritance for
-
# more details.
-
#
-
# == Connection to multiple databases in different models
-
#
-
# Connections are usually created through ActiveRecord::Base.establish_connection and retrieved
-
# by ActiveRecord::Base.connection. All classes inheriting from ActiveRecord::Base will use this
-
# connection. But you can also set a class-specific connection. For example, if Course is an
-
# ActiveRecord::Base, but resides in a different database, you can just say <tt>Course.establish_connection</tt>
-
# and Course and all of its subclasses will use this connection instead.
-
#
-
# This feature is implemented by keeping a connection pool in ActiveRecord::Base that is
-
# a Hash indexed by the class. If a connection is requested, the retrieve_connection method
-
# will go up the class-hierarchy until a connection is found in the connection pool.
-
#
-
# == Exceptions
-
#
-
# * ActiveRecordError - Generic error class and superclass of all other errors raised by Active Record.
-
# * AdapterNotSpecified - The configuration hash used in <tt>establish_connection</tt> didn't include an
-
# <tt>:adapter</tt> key.
-
# * AdapterNotFound - The <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified a
-
# non-existent adapter
-
# (or a bad spelling of an existing one).
-
# * AssociationTypeMismatch - The object assigned to the association wasn't of the type
-
# specified in the association definition.
-
# * AttributeAssignmentError - An error occurred while doing a mass assignment through the
-
# <tt>attributes=</tt> method.
-
# You can inspect the +attribute+ property of the exception object to determine which attribute
-
# triggered the error.
-
# * ConnectionNotEstablished - No connection has been established. Use <tt>establish_connection</tt>
-
# before querying.
-
# * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the
-
# <tt>attributes=</tt> method. The +errors+ property of this exception contains an array of
-
# AttributeAssignmentError
-
# objects that should be inspected to determine which attributes triggered the errors.
-
# * RecordInvalid - raised by save! and create! when the record is invalid.
-
# * RecordNotFound - No record responded to the +find+ method. Either the row with the given ID doesn't exist
-
# or the row didn't meet the additional restrictions. Some +find+ calls do not raise this exception to signal
-
# nothing was found, please check its documentation for further details.
-
# * SerializationTypeMismatch - The serialized object wasn't of the class specified as the second parameter.
-
# * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message.
-
#
-
# *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level).
-
# So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
-
# instances in the current object space.
-
1
class Base
-
1
extend ActiveModel::Naming
-
-
1
extend ActiveSupport::Benchmarkable
-
1
extend ActiveSupport::DescendantsTracker
-
-
1
extend ConnectionHandling
-
1
extend QueryCache::ClassMethods
-
1
extend Querying
-
1
extend Translation
-
1
extend DynamicMatchers
-
1
extend Explain
-
1
extend Enum
-
1
extend Delegation::DelegateCache
-
-
1
include Core
-
1
include Persistence
-
1
include ReadonlyAttributes
-
1
include ModelSchema
-
1
include Inheritance
-
1
include Scoping
-
1
include Sanitization
-
1
include AttributeAssignment
-
1
include ActiveModel::Conversion
-
1
include Integration
-
1
include Validations
-
1
include CounterCache
-
1
include Attributes
-
1
include AttributeDecorators
-
1
include Locking::Optimistic
-
1
include Locking::Pessimistic
-
1
include AttributeMethods
-
1
include Callbacks
-
1
include Timestamp
-
1
include Associations
-
1
include ActiveModel::SecurePassword
-
1
include AutosaveAssociation
-
1
include NestedAttributes
-
1
include Aggregations
-
1
include Transactions
-
1
include NoTouching
-
1
include Reflection
-
1
include Serialization
-
1
include Store
-
end
-
-
1
ActiveSupport.run_load_hooks(:active_record, Base)
-
end
-
1
module ActiveRecord
-
# = Active Record Callbacks
-
#
-
# Callbacks are hooks into the life cycle of an Active Record object that allow you to trigger logic
-
# before or after an alteration of the object state. This can be used to make sure that associated and
-
# dependent objects are deleted when +destroy+ is called (by overwriting +before_destroy+) or to massage attributes
-
# before they're validated (by overwriting +before_validation+). As an example of the callbacks initiated, consider
-
# the <tt>Base#save</tt> call for a new record:
-
#
-
# * (-) <tt>save</tt>
-
# * (-) <tt>valid</tt>
-
# * (1) <tt>before_validation</tt>
-
# * (-) <tt>validate</tt>
-
# * (2) <tt>after_validation</tt>
-
# * (3) <tt>before_save</tt>
-
# * (4) <tt>before_create</tt>
-
# * (-) <tt>create</tt>
-
# * (5) <tt>after_create</tt>
-
# * (6) <tt>after_save</tt>
-
# * (7) <tt>after_commit</tt>
-
#
-
# Also, an <tt>after_rollback</tt> callback can be configured to be triggered whenever a rollback is issued.
-
# Check out <tt>ActiveRecord::Transactions</tt> for more details about <tt>after_commit</tt> and
-
# <tt>after_rollback</tt>.
-
#
-
# Additionally, an <tt>after_touch</tt> callback is triggered whenever an
-
# object is touched.
-
#
-
# Lastly an <tt>after_find</tt> and <tt>after_initialize</tt> callback is triggered for each object that
-
# is found and instantiated by a finder, with <tt>after_initialize</tt> being triggered after new objects
-
# are instantiated as well.
-
#
-
# There are nineteen callbacks in total, which give you immense power to react and prepare for each state in the
-
# Active Record life cycle. The sequence for calling <tt>Base#save</tt> for an existing record is similar,
-
# except that each <tt>_create</tt> callback is replaced by the corresponding <tt>_update</tt> callback.
-
#
-
# Examples:
-
# class CreditCard < ActiveRecord::Base
-
# # Strip everything but digits, so the user can specify "555 234 34" or
-
# # "5552-3434" and both will mean "55523434"
-
# before_validation(on: :create) do
-
# self.number = number.gsub(/[^0-9]/, "") if attribute_present?("number")
-
# end
-
# end
-
#
-
# class Subscription < ActiveRecord::Base
-
# before_create :record_signup
-
#
-
# private
-
# def record_signup
-
# self.signed_up_on = Date.today
-
# end
-
# end
-
#
-
# class Firm < ActiveRecord::Base
-
# # Destroys the associated clients and people when the firm is destroyed
-
# before_destroy { |record| Person.destroy_all "firm_id = #{record.id}" }
-
# before_destroy { |record| Client.destroy_all "client_of = #{record.id}" }
-
# end
-
#
-
# == Inheritable callback queues
-
#
-
# Besides the overwritable callback methods, it's also possible to register callbacks through the
-
# use of the callback macros. Their main advantage is that the macros add behavior into a callback
-
# queue that is kept intact down through an inheritance hierarchy.
-
#
-
# class Topic < ActiveRecord::Base
-
# before_destroy :destroy_author
-
# end
-
#
-
# class Reply < Topic
-
# before_destroy :destroy_readers
-
# end
-
#
-
# Now, when <tt>Topic#destroy</tt> is run only +destroy_author+ is called. When <tt>Reply#destroy</tt> is
-
# run, both +destroy_author+ and +destroy_readers+ are called. Contrast this to the following situation
-
# where the +before_destroy+ method is overridden:
-
#
-
# class Topic < ActiveRecord::Base
-
# def before_destroy() destroy_author end
-
# end
-
#
-
# class Reply < Topic
-
# def before_destroy() destroy_readers end
-
# end
-
#
-
# In that case, <tt>Reply#destroy</tt> would only run +destroy_readers+ and _not_ +destroy_author+.
-
# So, use the callback macros when you want to ensure that a certain callback is called for the entire
-
# hierarchy, and use the regular overwritable methods when you want to leave it up to each descendant
-
# to decide whether they want to call +super+ and trigger the inherited callbacks.
-
#
-
# *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the
-
# callbacks before specifying the associations. Otherwise, you might trigger the loading of a
-
# child before the parent has registered the callbacks and they won't be inherited.
-
#
-
# == Types of callbacks
-
#
-
# There are four types of callbacks accepted by the callback macros: Method references (symbol), callback objects,
-
# inline methods (using a proc), and inline eval methods (using a string). Method references and callback objects
-
# are the recommended approaches, inline methods using a proc are sometimes appropriate (such as for
-
# creating mix-ins), and inline eval methods are deprecated.
-
#
-
# The method reference callbacks work by specifying a protected or private method available in the object, like this:
-
#
-
# class Topic < ActiveRecord::Base
-
# before_destroy :delete_parents
-
#
-
# private
-
# def delete_parents
-
# self.class.delete_all "parent_id = #{id}"
-
# end
-
# end
-
#
-
# The callback objects have methods named after the callback called with the record as the only parameter, such as:
-
#
-
# class BankAccount < ActiveRecord::Base
-
# before_save EncryptionWrapper.new
-
# after_save EncryptionWrapper.new
-
# after_initialize EncryptionWrapper.new
-
# end
-
#
-
# class EncryptionWrapper
-
# def before_save(record)
-
# record.credit_card_number = encrypt(record.credit_card_number)
-
# end
-
#
-
# def after_save(record)
-
# record.credit_card_number = decrypt(record.credit_card_number)
-
# end
-
#
-
# alias_method :after_initialize, :after_save
-
#
-
# private
-
# def encrypt(value)
-
# # Secrecy is committed
-
# end
-
#
-
# def decrypt(value)
-
# # Secrecy is unveiled
-
# end
-
# end
-
#
-
# So you specify the object you want messaged on a given callback. When that callback is triggered, the object has
-
# a method by the name of the callback messaged. You can make these callbacks more flexible by passing in other
-
# initialization data such as the name of the attribute to work with:
-
#
-
# class BankAccount < ActiveRecord::Base
-
# before_save EncryptionWrapper.new("credit_card_number")
-
# after_save EncryptionWrapper.new("credit_card_number")
-
# after_initialize EncryptionWrapper.new("credit_card_number")
-
# end
-
#
-
# class EncryptionWrapper
-
# def initialize(attribute)
-
# @attribute = attribute
-
# end
-
#
-
# def before_save(record)
-
# record.send("#{@attribute}=", encrypt(record.send("#{@attribute}")))
-
# end
-
#
-
# def after_save(record)
-
# record.send("#{@attribute}=", decrypt(record.send("#{@attribute}")))
-
# end
-
#
-
# alias_method :after_initialize, :after_save
-
#
-
# private
-
# def encrypt(value)
-
# # Secrecy is committed
-
# end
-
#
-
# def decrypt(value)
-
# # Secrecy is unveiled
-
# end
-
# end
-
#
-
# The callback macros usually accept a symbol for the method they're supposed to run, but you can also
-
# pass a "method string", which will then be evaluated within the binding of the callback. Example:
-
#
-
# class Topic < ActiveRecord::Base
-
# before_destroy 'self.class.delete_all "parent_id = #{id}"'
-
# end
-
#
-
# Notice that single quotes (') are used so the <tt>#{id}</tt> part isn't evaluated until the callback
-
# is triggered. Also note that these inline callbacks can be stacked just like the regular ones:
-
#
-
# class Topic < ActiveRecord::Base
-
# before_destroy 'self.class.delete_all "parent_id = #{id}"',
-
# 'puts "Evaluated after parents are destroyed"'
-
# end
-
#
-
# == <tt>before_validation*</tt> returning statements
-
#
-
# If the returning value of a +before_validation+ callback can be evaluated to +false+, the process will be
-
# aborted and <tt>Base#save</tt> will return +false+. If Base#save! is called it will raise a
-
# ActiveRecord::RecordInvalid exception. Nothing will be appended to the errors object.
-
#
-
# == Canceling callbacks
-
#
-
# If a <tt>before_*</tt> callback returns +false+, all the later callbacks and the associated action are
-
# cancelled.
-
# Callbacks are generally run in the order they are defined, with the exception of callbacks defined as
-
# methods on the model, which are called last.
-
#
-
# == Ordering callbacks
-
#
-
# Sometimes the code needs that the callbacks execute in a specific order. For example, a +before_destroy+
-
# callback (+log_children+ in this case) should be executed before the children get destroyed by the +dependent: destroy+ option.
-
#
-
# Let's look at the code below:
-
#
-
# class Topic < ActiveRecord::Base
-
# has_many :children, dependent: destroy
-
#
-
# before_destroy :log_children
-
#
-
# private
-
# def log_children
-
# # Child processing
-
# end
-
# end
-
#
-
# In this case, the problem is that when the +before_destroy+ callback is executed, the children are not available
-
# because the +destroy+ callback gets executed first. You can use the +prepend+ option on the +before_destroy+ callback to avoid this.
-
#
-
# class Topic < ActiveRecord::Base
-
# has_many :children, dependent: destroy
-
#
-
# before_destroy :log_children, prepend: true
-
#
-
# private
-
# def log_children
-
# # Child processing
-
# end
-
# end
-
#
-
# This way, the +before_destroy+ gets executed before the <tt>dependent: destroy</tt> is called, and the data is still available.
-
#
-
# == Transactions
-
#
-
# The entire callback chain of a +save+, <tt>save!</tt>, or +destroy+ call runs
-
# within a transaction. That includes <tt>after_*</tt> hooks. If everything
-
# goes fine a COMMIT is executed once the chain has been completed.
-
#
-
# If a <tt>before_*</tt> callback cancels the action a ROLLBACK is issued. You
-
# can also trigger a ROLLBACK raising an exception in any of the callbacks,
-
# including <tt>after_*</tt> hooks. Note, however, that in that case the client
-
# needs to be aware of it because an ordinary +save+ will raise such exception
-
# instead of quietly returning +false+.
-
#
-
# == Debugging callbacks
-
#
-
# The callback chain is accessible via the <tt>_*_callbacks</tt> method on an object. ActiveModel Callbacks support
-
# <tt>:before</tt>, <tt>:after</tt> and <tt>:around</tt> as values for the <tt>kind</tt> property. The <tt>kind</tt> property
-
# defines what part of the chain the callback runs in.
-
#
-
# To find all callbacks in the before_save callback chain:
-
#
-
# Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }
-
#
-
# Returns an array of callback objects that form the before_save chain.
-
#
-
# To further check if the before_save chain contains a proc defined as <tt>rest_when_dead</tt> use the <tt>filter</tt> property of the callback object:
-
#
-
# Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }.collect(&:filter).include?(:rest_when_dead)
-
#
-
# Returns true or false depending on whether the proc is contained in the before_save callback chain on a Topic model.
-
#
-
1
module Callbacks
-
1
extend ActiveSupport::Concern
-
-
1
CALLBACKS = [
-
:after_initialize, :after_find, :after_touch, :before_validation, :after_validation,
-
:before_save, :around_save, :after_save, :before_create, :around_create,
-
:after_create, :before_update, :around_update, :after_update,
-
:before_destroy, :around_destroy, :after_destroy, :after_commit, :after_rollback
-
]
-
-
1
module ClassMethods
-
1
include ActiveModel::Callbacks
-
end
-
-
1
included do
-
1
include ActiveModel::Validations::Callbacks
-
-
1
define_model_callbacks :initialize, :find, :touch, :only => :after
-
1
define_model_callbacks :save, :create, :update, :destroy
-
end
-
-
1
def destroy #:nodoc:
-
2
_run_destroy_callbacks { super }
-
end
-
-
1
def touch(*) #:nodoc:
-
_run_touch_callbacks { super }
-
end
-
-
1
private
-
-
1
def create_or_update #:nodoc:
-
62
_run_save_callbacks { super }
-
end
-
-
1
def _create_record #:nodoc:
-
56
_run_create_callbacks { super }
-
end
-
-
1
def _update_record(*) #:nodoc:
-
6
_run_update_callbacks { super }
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module Savepoints #:nodoc:
-
1
def supports_savepoints?
-
true
-
end
-
-
1
def create_savepoint(name = current_savepoint_name)
-
36
execute("SAVEPOINT #{name}")
-
end
-
-
1
def exec_rollback_to_savepoint(name = current_savepoint_name)
-
4
execute("ROLLBACK TO SAVEPOINT #{name}")
-
end
-
-
1
def release_savepoint(name = current_savepoint_name)
-
32
execute("RELEASE SAVEPOINT #{name}")
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
class TransactionState
-
1
attr_reader :parent
-
-
1
VALID_STATES = Set.new([:committed, :rolledback, nil])
-
-
1
def initialize(state = nil)
-
64
@state = state
-
64
@parent = nil
-
end
-
-
1
def finalized?
-
133
@state
-
end
-
-
1
def committed?
-
38
@state == :committed
-
end
-
-
1
def rolledback?
-
12
@state == :rolledback
-
end
-
-
1
def completed?
-
committed? || rolledback?
-
end
-
-
1
def set_state(state)
-
64
if !VALID_STATES.include?(state)
-
raise ArgumentError, "Invalid transaction state: #{state}"
-
end
-
64
@state = state
-
end
-
end
-
-
1
class NullTransaction #:nodoc:
-
1
def initialize; end
-
1
def closed?; true; end
-
1
def open?; false; end
-
1
def joinable?; false; end
-
1
def add_record(record); end
-
end
-
-
1
class Transaction #:nodoc:
-
-
1
attr_reader :connection, :state, :records, :savepoint_name
-
1
attr_writer :joinable
-
-
1
def initialize(connection, options)
-
64
@connection = connection
-
64
@state = TransactionState.new
-
64
@records = []
-
64
@joinable = options.fetch(:joinable, true)
-
end
-
-
1
def add_record(record)
-
2
records << record
-
end
-
-
1
def rollback
-
32
@state.set_state(:rolledback)
-
end
-
-
1
def rollback_records
-
32
ite = records.uniq
-
32
while record = ite.shift
-
1
begin
-
1
record.rolledback! full_rollback?
-
rescue => e
-
raise if ActiveRecord::Base.raise_in_transactional_callbacks
-
record.logger.error(e) if record.respond_to?(:logger) && record.logger
-
end
-
end
-
ensure
-
32
ite.each do |i|
-
i.rolledback!(full_rollback?, false)
-
end
-
end
-
-
1
def commit
-
32
@state.set_state(:committed)
-
end
-
-
1
def commit_records
-
ite = records.uniq
-
while record = ite.shift
-
begin
-
record.committed!
-
rescue => e
-
raise if ActiveRecord::Base.raise_in_transactional_callbacks
-
record.logger.error(e) if record.respond_to?(:logger) && record.logger
-
end
-
end
-
ensure
-
ite.each do |i|
-
i.committed!(false)
-
end
-
end
-
-
2
def full_rollback?; true; end
-
39
def joinable?; @joinable; end
-
29
def closed?; false; end
-
29
def open?; !closed?; end
-
end
-
-
1
class SavepointTransaction < Transaction
-
-
1
def initialize(connection, savepoint_name, options)
-
36
super(connection, options)
-
36
if options[:isolation]
-
raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
-
end
-
36
connection.create_savepoint(@savepoint_name = savepoint_name)
-
end
-
-
1
def rollback
-
4
connection.rollback_to_savepoint(savepoint_name)
-
4
super
-
4
rollback_records
-
end
-
-
1
def commit
-
32
connection.release_savepoint(savepoint_name)
-
32
super
-
32
parent = connection.transaction_manager.current_transaction
-
33
records.each { |r| parent.add_record(r) }
-
end
-
-
1
def full_rollback?; false; end
-
end
-
-
1
class RealTransaction < Transaction
-
-
1
def initialize(connection, options)
-
28
super
-
28
if options[:isolation]
-
connection.begin_isolated_db_transaction(options[:isolation])
-
else
-
28
connection.begin_db_transaction
-
end
-
end
-
-
1
def rollback
-
28
connection.rollback_db_transaction
-
28
super
-
28
rollback_records
-
end
-
-
1
def commit
-
connection.commit_db_transaction
-
super
-
commit_records
-
end
-
end
-
-
1
class TransactionManager #:nodoc:
-
1
def initialize(connection)
-
3
@stack = []
-
3
@connection = connection
-
end
-
-
1
def begin_transaction(options = {})
-
64
transaction =
-
if @stack.empty?
-
28
RealTransaction.new(@connection, options)
-
else
-
36
SavepointTransaction.new(@connection, "active_record_#{@stack.size}", options)
-
end
-
64
@stack.push(transaction)
-
64
transaction
-
end
-
-
1
def commit_transaction
-
32
@stack.pop.commit
-
end
-
-
1
def rollback_transaction
-
32
@stack.pop.rollback
-
end
-
-
1
def within_new_transaction(options = {})
-
36
transaction = begin_transaction options
-
36
yield
-
rescue Exception => error
-
4
rollback_transaction if transaction
-
4
raise
-
ensure
-
36
unless error
-
32
if Thread.current.status == 'aborting'
-
rollback_transaction if transaction
-
else
-
32
begin
-
32
commit_transaction
-
rescue Exception
-
transaction.rollback unless transaction.state.completed?
-
raise
-
end
-
end
-
end
-
end
-
-
1
def open_transactions
-
@stack.size
-
end
-
-
1
def current_transaction
-
137
@stack.last || NULL_TRANSACTION
-
end
-
-
1
private
-
1
NULL_TRANSACTION = NullTransaction.new
-
end
-
end
-
end
-
1
require 'arel/visitors/bind_visitor'
-
1
require 'active_support/core_ext/string/strip'
-
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
class AbstractMysqlAdapter < AbstractAdapter
-
1
include Savepoints
-
-
1
class SchemaCreation < AbstractAdapter::SchemaCreation
-
1
def visit_AddColumn(o)
-
add_column_position!(super, column_options(o))
-
end
-
-
1
private
-
-
1
def visit_DropForeignKey(name)
-
"DROP FOREIGN KEY #{name}"
-
end
-
-
1
def visit_TableDefinition(o)
-
name = o.name
-
create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(name)} "
-
-
statements = o.columns.map { |c| accept c }
-
statements.concat(o.indexes.map { |column_name, options| index_in_create(name, column_name, options) })
-
-
create_sql << "(#{statements.join(', ')}) " if statements.present?
-
create_sql << "#{o.options}"
-
create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
-
create_sql
-
end
-
-
1
def visit_ChangeColumnDefinition(o)
-
column = o.column
-
options = o.options
-
sql_type = type_to_sql(o.type, options[:limit], options[:precision], options[:scale])
-
change_column_sql = "CHANGE #{quote_column_name(column.name)} #{quote_column_name(options[:name])} #{sql_type}"
-
add_column_options!(change_column_sql, options.merge(column: column))
-
add_column_position!(change_column_sql, options)
-
end
-
-
1
def add_column_position!(sql, options)
-
if options[:first]
-
sql << " FIRST"
-
elsif options[:after]
-
sql << " AFTER #{quote_column_name(options[:after])}"
-
end
-
sql
-
end
-
-
1
def index_in_create(table_name, column_name, options)
-
index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.add_index_options(table_name, column_name, options)
-
"#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}"
-
end
-
end
-
-
1
def schema_creation
-
SchemaCreation.new self
-
end
-
-
1
def prepare_column_options(column, types) # :nodoc:
-
spec = super
-
spec.delete(:limit) if :boolean === column.type
-
spec
-
end
-
-
1
class Column < ConnectionAdapters::Column # :nodoc:
-
1
attr_reader :collation, :strict, :extra
-
-
1
def initialize(name, default, cast_type, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
-
@strict = strict
-
@collation = collation
-
@extra = extra
-
super(name, default, cast_type, sql_type, null)
-
assert_valid_default(default)
-
extract_default
-
end
-
-
1
def extract_default
-
if blob_or_text_column?
-
@default = null || strict ? nil : ''
-
elsif missing_default_forged_as_empty_string?(@default)
-
@default = nil
-
end
-
end
-
-
1
def has_default?
-
return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
-
super
-
end
-
-
1
def blob_or_text_column?
-
sql_type =~ /blob/i || type == :text
-
end
-
-
1
def case_sensitive?
-
collation && !collation.match(/_ci$/)
-
end
-
-
1
def ==(other)
-
super &&
-
collation == other.collation &&
-
strict == other.strict &&
-
extra == other.extra
-
end
-
-
1
private
-
-
# MySQL misreports NOT NULL column default when none is given.
-
# We can't detect this for columns which may have a legitimate ''
-
# default (string) but we can for others (integer, datetime, boolean,
-
# and the rest).
-
#
-
# Test whether the column has default '', is not null, and is not
-
# a type allowing default ''.
-
1
def missing_default_forged_as_empty_string?(default)
-
type != :string && !null && default == ''
-
end
-
-
1
def assert_valid_default(default)
-
if blob_or_text_column? && default.present?
-
raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
-
end
-
end
-
-
1
def attributes_for_hash
-
super + [collation, strict, extra]
-
end
-
end
-
-
##
-
# :singleton-method:
-
# By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
-
# as boolean. If you wish to disable this emulation (which was the default
-
# behavior in versions 0.13.1 and earlier) you can add the following line
-
# to your application.rb file:
-
#
-
# ActiveRecord::ConnectionAdapters::Mysql[2]Adapter.emulate_booleans = false
-
1
class_attribute :emulate_booleans
-
1
self.emulate_booleans = true
-
-
1
LOST_CONNECTION_ERROR_MESSAGES = [
-
"Server shutdown in progress",
-
"Broken pipe",
-
"Lost connection to MySQL server during query",
-
"MySQL server has gone away" ]
-
-
1
QUOTED_TRUE, QUOTED_FALSE = '1', '0'
-
-
1
NATIVE_DATABASE_TYPES = {
-
:primary_key => "int(11) auto_increment PRIMARY KEY",
-
:string => { :name => "varchar", :limit => 255 },
-
:text => { :name => "text" },
-
:integer => { :name => "int", :limit => 4 },
-
:float => { :name => "float" },
-
:decimal => { :name => "decimal" },
-
:datetime => { :name => "datetime" },
-
:time => { :name => "time" },
-
:date => { :name => "date" },
-
:binary => { :name => "blob" },
-
:boolean => { :name => "tinyint", :limit => 1 }
-
}
-
-
1
INDEX_TYPES = [:fulltext, :spatial]
-
1
INDEX_USINGS = [:btree, :hash]
-
-
# FIXME: Make the first parameter more similar for the two adapters
-
1
def initialize(connection, logger, connection_options, config)
-
super(connection, logger)
-
@connection_options, @config = connection_options, config
-
@quoted_column_names, @quoted_table_names = {}, {}
-
-
@visitor = Arel::Visitors::MySQL.new self
-
-
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
-
@prepared_statements = true
-
else
-
@prepared_statements = false
-
end
-
end
-
-
# Returns true, since this connection adapter supports migrations.
-
1
def supports_migrations?
-
true
-
end
-
-
1
def supports_primary_key?
-
true
-
end
-
-
1
def supports_bulk_alter? #:nodoc:
-
true
-
end
-
-
# Technically MySQL allows to create indexes with the sort order syntax
-
# but at the moment (5.5) it doesn't yet implement them
-
1
def supports_index_sort_order?
-
true
-
end
-
-
# MySQL 4 technically support transaction isolation, but it is affected by a bug
-
# where the transaction level gets persisted for the whole session:
-
#
-
# http://bugs.mysql.com/bug.php?id=39170
-
1
def supports_transaction_isolation?
-
version[0] >= 5
-
end
-
-
1
def supports_indexes_in_create?
-
true
-
end
-
-
1
def supports_foreign_keys?
-
true
-
end
-
-
1
def supports_views?
-
version[0] >= 5
-
end
-
-
1
def native_database_types
-
NATIVE_DATABASE_TYPES
-
end
-
-
1
def index_algorithms
-
{ default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' }
-
end
-
-
# HELPER METHODS ===========================================
-
-
# The two drivers have slightly different ways of yielding hashes of results, so
-
# this method must be implemented to provide a uniform interface.
-
1
def each_hash(result) # :nodoc:
-
raise NotImplementedError
-
end
-
-
1
def new_column(field, default, cast_type, sql_type = nil, null = true, collation = "", extra = "") # :nodoc:
-
Column.new(field, default, cast_type, sql_type, null, collation, strict_mode?, extra)
-
end
-
-
# Must return the MySQL error number from the exception, if the exception has an
-
# error number.
-
1
def error_number(exception) # :nodoc:
-
raise NotImplementedError
-
end
-
-
# QUOTING ==================================================
-
-
1
def _quote(value) # :nodoc:
-
if value.is_a?(Type::Binary::Data)
-
"x'#{value.hex}'"
-
else
-
super
-
end
-
end
-
-
1
def quote_column_name(name) #:nodoc:
-
@quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
-
end
-
-
1
def quote_table_name(name) #:nodoc:
-
@quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
-
end
-
-
1
def quoted_true
-
QUOTED_TRUE
-
end
-
-
1
def unquoted_true
-
1
-
end
-
-
1
def quoted_false
-
QUOTED_FALSE
-
end
-
-
1
def unquoted_false
-
0
-
end
-
-
# REFERENTIAL INTEGRITY ====================================
-
-
1
def disable_referential_integrity #:nodoc:
-
old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
-
-
begin
-
update("SET FOREIGN_KEY_CHECKS = 0")
-
yield
-
ensure
-
update("SET FOREIGN_KEY_CHECKS = #{old}")
-
end
-
end
-
-
#--
-
# DATABASE STATEMENTS ======================================
-
#++
-
-
1
def clear_cache!
-
super
-
reload_type_map
-
end
-
-
# Executes the SQL statement in the context of this connection.
-
1
def execute(sql, name = nil)
-
log(sql, name) { @connection.query(sql) }
-
end
-
-
# MysqlAdapter has to free a result after using it, so we use this method to write
-
# stuff in an abstract way without concerning ourselves about whether it needs to be
-
# explicitly freed or not.
-
1
def execute_and_free(sql, name = nil) #:nodoc:
-
yield execute(sql, name)
-
end
-
-
1
def update_sql(sql, name = nil) #:nodoc:
-
super
-
@connection.affected_rows
-
end
-
-
1
def begin_db_transaction
-
execute "BEGIN"
-
end
-
-
1
def begin_isolated_db_transaction(isolation)
-
execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
-
begin_db_transaction
-
end
-
-
1
def commit_db_transaction #:nodoc:
-
execute "COMMIT"
-
end
-
-
1
def exec_rollback_db_transaction #:nodoc:
-
execute "ROLLBACK"
-
end
-
-
# In the simple case, MySQL allows us to place JOINs directly into the UPDATE
-
# query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
-
# these, we must use a subquery.
-
1
def join_to_update(update, select) #:nodoc:
-
if select.limit || select.offset || select.orders.any?
-
super
-
else
-
update.table select.source
-
update.wheres = select.constraints
-
end
-
end
-
-
1
def empty_insert_statement_value
-
"VALUES ()"
-
end
-
-
# SCHEMA STATEMENTS ========================================
-
-
# Drops the database specified on the +name+ attribute
-
# and creates it again using the provided +options+.
-
1
def recreate_database(name, options = {})
-
drop_database(name)
-
sql = create_database(name, options)
-
reconnect!
-
sql
-
end
-
-
# Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
-
# Charset defaults to utf8.
-
#
-
# Example:
-
# create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
-
# create_database 'matt_development'
-
# create_database 'matt_development', charset: :big5
-
1
def create_database(name, options = {})
-
if options[:collation]
-
execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
-
else
-
execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
-
end
-
end
-
-
# Drops a MySQL database.
-
#
-
# Example:
-
# drop_database('sebastian_development')
-
1
def drop_database(name) #:nodoc:
-
execute "DROP DATABASE IF EXISTS `#{name}`"
-
end
-
-
1
def current_database
-
select_value 'SELECT DATABASE() as db'
-
end
-
-
# Returns the database character set.
-
1
def charset
-
show_variable 'character_set_database'
-
end
-
-
# Returns the database collation strategy.
-
1
def collation
-
show_variable 'collation_database'
-
end
-
-
1
def tables(name = nil, database = nil, like = nil) #:nodoc:
-
sql = "SHOW TABLES "
-
sql << "IN #{quote_table_name(database)} " if database
-
sql << "LIKE #{quote(like)}" if like
-
-
execute_and_free(sql, 'SCHEMA') do |result|
-
result.collect { |field| field.first }
-
end
-
end
-
-
1
def truncate(table_name, name = nil)
-
execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
-
end
-
-
1
def table_exists?(name)
-
return false unless name.present?
-
return true if tables(nil, nil, name).any?
-
-
name = name.to_s
-
schema, table = name.split('.', 2)
-
-
unless table # A table was provided without a schema
-
table = schema
-
schema = nil
-
end
-
-
tables(nil, schema, table).any?
-
end
-
-
# Returns an array of indexes for the given table.
-
1
def indexes(table_name, name = nil) #:nodoc:
-
indexes = []
-
current_index = nil
-
execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
-
each_hash(result) do |row|
-
if current_index != row[:Key_name]
-
next if row[:Key_name] == 'PRIMARY' # skip the primary key
-
current_index = row[:Key_name]
-
-
mysql_index_type = row[:Index_type].downcase.to_sym
-
index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
-
index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
-
indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using)
-
end
-
-
indexes.last.columns << row[:Column_name]
-
indexes.last.lengths << row[:Sub_part]
-
end
-
end
-
-
indexes
-
end
-
-
# Returns an array of +Column+ objects for the table specified by +table_name+.
-
1
def columns(table_name)#:nodoc:
-
sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
-
execute_and_free(sql, 'SCHEMA') do |result|
-
each_hash(result).map do |field|
-
field_name = set_field_encoding(field[:Field])
-
sql_type = field[:Type]
-
cast_type = lookup_cast_type(sql_type)
-
new_column(field_name, field[:Default], cast_type, sql_type, field[:Null] == "YES", field[:Collation], field[:Extra])
-
end
-
end
-
end
-
-
1
def create_table(table_name, options = {}) #:nodoc:
-
super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
-
end
-
-
1
def bulk_change_table(table_name, operations) #:nodoc:
-
sqls = operations.flat_map do |command, args|
-
table, arguments = args.shift, args
-
method = :"#{command}_sql"
-
-
if respond_to?(method, true)
-
send(method, table, *arguments)
-
else
-
raise "Unknown method called : #{method}(#{arguments.inspect})"
-
end
-
end.join(", ")
-
-
execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
-
end
-
-
# Renames a table.
-
#
-
# Example:
-
# rename_table('octopuses', 'octopi')
-
1
def rename_table(table_name, new_name)
-
execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
-
rename_table_indexes(table_name, new_name)
-
end
-
-
1
def drop_table(table_name, options = {})
-
execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
-
end
-
-
1
def rename_index(table_name, old_name, new_name)
-
if supports_rename_index?
-
validate_index_length!(table_name, new_name)
-
-
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
-
else
-
super
-
end
-
end
-
-
1
def change_column_default(table_name, column_name, default) #:nodoc:
-
column = column_for(table_name, column_name)
-
change_column table_name, column_name, column.sql_type, :default => default
-
end
-
-
1
def change_column_null(table_name, column_name, null, default = nil)
-
column = column_for(table_name, column_name)
-
-
unless null || default.nil?
-
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
-
end
-
-
change_column table_name, column_name, column.sql_type, :null => null
-
end
-
-
1
def change_column(table_name, column_name, type, options = {}) #:nodoc:
-
execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}")
-
end
-
-
1
def rename_column(table_name, column_name, new_column_name) #:nodoc:
-
execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
-
rename_column_indexes(table_name, column_name, new_column_name)
-
end
-
-
1
def add_index(table_name, column_name, options = {}) #:nodoc:
-
index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
-
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options} #{index_algorithm}"
-
end
-
-
1
def foreign_keys(table_name)
-
fk_info = select_all <<-SQL.strip_heredoc
-
SELECT fk.referenced_table_name as 'to_table'
-
,fk.referenced_column_name as 'primary_key'
-
,fk.column_name as 'column'
-
,fk.constraint_name as 'name'
-
FROM information_schema.key_column_usage fk
-
WHERE fk.referenced_column_name is not null
-
AND fk.table_schema = '#{@config[:database]}'
-
AND fk.table_name = '#{table_name}'
-
SQL
-
-
create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
-
-
fk_info.map do |row|
-
options = {
-
column: row['column'],
-
name: row['name'],
-
primary_key: row['primary_key']
-
}
-
-
options[:on_update] = extract_foreign_key_action(create_table_info, row['name'], "UPDATE")
-
options[:on_delete] = extract_foreign_key_action(create_table_info, row['name'], "DELETE")
-
-
ForeignKeyDefinition.new(table_name, row['to_table'], options)
-
end
-
end
-
-
# Maps logical Rails types to MySQL-specific data types.
-
1
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
-
case type.to_s
-
when 'binary'
-
case limit
-
when 0..0xfff; "varbinary(#{limit})"
-
when nil; "blob"
-
when 0x1000..0xffffffff; "blob(#{limit})"
-
else raise(ActiveRecordError, "No binary type has character length #{limit}")
-
end
-
when 'integer'
-
case limit
-
when 1; 'tinyint'
-
when 2; 'smallint'
-
when 3; 'mediumint'
-
when nil, 4, 11; 'int(11)' # compatibility with MySQL default
-
when 5..8; 'bigint'
-
else raise(ActiveRecordError, "No integer type has byte size #{limit}")
-
end
-
when 'text'
-
case limit
-
when 0..0xff; 'tinytext'
-
when nil, 0x100..0xffff; 'text'
-
when 0x10000..0xffffff; 'mediumtext'
-
when 0x1000000..0xffffffff; 'longtext'
-
else raise(ActiveRecordError, "No text type has character length #{limit}")
-
end
-
when 'datetime'
-
return super unless precision
-
-
case precision
-
when 0..6; "datetime(#{precision})"
-
else raise(ActiveRecordError, "No datetime type has precision of #{precision}. The allowed range of precision is from 0 to 6.")
-
end
-
else
-
super
-
end
-
end
-
-
# SHOW VARIABLES LIKE 'name'
-
1
def show_variable(name)
-
variables = select_all("SHOW VARIABLES LIKE '#{name}'", 'SCHEMA')
-
variables.first['Value'] unless variables.empty?
-
end
-
-
# Returns a table's primary key and belonging sequence.
-
1
def pk_and_sequence_for(table)
-
execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
-
create_table = each_hash(result).first[:"Create Table"]
-
if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
-
keys = $1.split(",").map { |key| key.delete('`"') }
-
keys.length == 1 ? [keys.first, nil] : nil
-
else
-
nil
-
end
-
end
-
end
-
-
# Returns just a table's primary key
-
1
def primary_key(table)
-
pk_and_sequence = pk_and_sequence_for(table)
-
pk_and_sequence && pk_and_sequence.first
-
end
-
-
1
def case_sensitive_modifier(node, table_attribute)
-
node = Arel::Nodes.build_quoted node, table_attribute
-
Arel::Nodes::Bin.new(node)
-
end
-
-
1
def case_sensitive_comparison(table, attribute, column, value)
-
if column.case_sensitive?
-
table[attribute].eq(value)
-
else
-
super
-
end
-
end
-
-
1
def case_insensitive_comparison(table, attribute, column, value)
-
if column.case_sensitive?
-
super
-
else
-
table[attribute].eq(value)
-
end
-
end
-
-
1
def strict_mode?
-
self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
-
end
-
-
1
def valid_type?(type)
-
!native_database_types[type].nil?
-
end
-
-
1
protected
-
-
1
def initialize_type_map(m) # :nodoc:
-
super
-
-
register_class_with_limit m, %r(char)i, MysqlString
-
-
m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
-
m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
-
m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
-
m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
-
m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
-
m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
-
m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
-
m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
-
m.register_type %r(^float)i, Type::Float.new(limit: 24)
-
m.register_type %r(^double)i, Type::Float.new(limit: 53)
-
-
register_integer_type m, %r(^bigint)i, limit: 8
-
register_integer_type m, %r(^int)i, limit: 4
-
register_integer_type m, %r(^mediumint)i, limit: 3
-
register_integer_type m, %r(^smallint)i, limit: 2
-
register_integer_type m, %r(^tinyint)i, limit: 1
-
-
m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans
-
m.alias_type %r(set)i, 'varchar'
-
m.alias_type %r(year)i, 'integer'
-
m.alias_type %r(bit)i, 'binary'
-
-
m.register_type(%r(datetime)i) do |sql_type|
-
precision = extract_precision(sql_type)
-
MysqlDateTime.new(precision: precision)
-
end
-
-
m.register_type(%r(enum)i) do |sql_type|
-
limit = sql_type[/^enum\((.+)\)/i, 1]
-
.split(',').map{|enum| enum.strip.length - 2}.max
-
MysqlString.new(limit: limit)
-
end
-
end
-
-
1
def register_integer_type(mapping, key, options) # :nodoc:
-
mapping.register_type(key) do |sql_type|
-
if /unsigned/i =~ sql_type
-
Type::UnsignedInteger.new(options)
-
else
-
Type::Integer.new(options)
-
end
-
end
-
end
-
-
# MySQL is too stupid to create a temporary table for use subquery, so we have
-
# to give it some prompting in the form of a subsubquery. Ugh!
-
1
def subquery_for(key, select)
-
subsubselect = select.clone
-
subsubselect.projections = [key]
-
-
subselect = Arel::SelectManager.new(select.engine)
-
subselect.project Arel.sql(key.name)
-
subselect.from subsubselect.as('__active_record_temp')
-
end
-
-
1
def add_index_length(option_strings, column_names, options = {})
-
if options.is_a?(Hash) && length = options[:length]
-
case length
-
when Hash
-
column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
-
when Fixnum
-
column_names.each {|name| option_strings[name] += "(#{length})"}
-
end
-
end
-
-
return option_strings
-
end
-
-
1
def quoted_columns_for_index(column_names, options = {})
-
option_strings = Hash[column_names.map {|name| [name, '']}]
-
-
# add index length
-
option_strings = add_index_length(option_strings, column_names, options)
-
-
# add index sort order
-
option_strings = add_index_sort_order(option_strings, column_names, options)
-
-
column_names.map {|name| quote_column_name(name) + option_strings[name]}
-
end
-
-
1
def translate_exception(exception, message)
-
case error_number(exception)
-
when 1062
-
RecordNotUnique.new(message, exception)
-
when 1452
-
InvalidForeignKey.new(message, exception)
-
else
-
super
-
end
-
end
-
-
1
def add_column_sql(table_name, column_name, type, options = {})
-
td = create_table_definition table_name, options[:temporary], options[:options]
-
cd = td.new_column_definition(column_name, type, options)
-
schema_creation.visit_AddColumn cd
-
end
-
-
1
def change_column_sql(table_name, column_name, type, options = {})
-
column = column_for(table_name, column_name)
-
-
unless options_include_default?(options)
-
options[:default] = column.default
-
end
-
-
unless options.has_key?(:null)
-
options[:null] = column.null
-
end
-
-
options[:name] = column.name
-
schema_creation.accept ChangeColumnDefinition.new column, type, options
-
end
-
-
1
def rename_column_sql(table_name, column_name, new_column_name)
-
column = column_for(table_name, column_name)
-
options = {
-
name: new_column_name,
-
default: column.default,
-
null: column.null,
-
auto_increment: column.extra == "auto_increment"
-
}
-
-
current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
-
schema_creation.accept ChangeColumnDefinition.new column, current_type, options
-
end
-
-
1
def remove_column_sql(table_name, column_name, type = nil, options = {})
-
"DROP #{quote_column_name(column_name)}"
-
end
-
-
1
def remove_columns_sql(table_name, *column_names)
-
column_names.map {|column_name| remove_column_sql(table_name, column_name) }
-
end
-
-
1
def add_index_sql(table_name, column_name, options = {})
-
index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
-
"ADD #{index_type} INDEX #{index_name} (#{index_columns})"
-
end
-
-
1
def remove_index_sql(table_name, options = {})
-
index_name = index_name_for_remove(table_name, options)
-
"DROP INDEX #{index_name}"
-
end
-
-
1
def add_timestamps_sql(table_name, options = {})
-
[add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)]
-
end
-
-
1
def remove_timestamps_sql(table_name, options = {})
-
[remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
-
end
-
-
1
private
-
-
1
def version
-
@version ||= full_version.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
-
end
-
-
1
def mariadb?
-
full_version =~ /mariadb/i
-
end
-
-
1
def supports_rename_index?
-
mariadb? ? false : (version[0] == 5 && version[1] >= 7) || version[0] >= 6
-
end
-
-
1
def configure_connection
-
variables = @config.fetch(:variables, {}).stringify_keys
-
-
# By default, MySQL 'where id is null' selects the last inserted id.
-
# Turn this off. http://dev.rubyonrails.org/ticket/6778
-
variables['sql_auto_is_null'] = 0
-
-
# Increase timeout so the server doesn't disconnect us.
-
wait_timeout = @config[:wait_timeout]
-
wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
-
variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout)
-
-
# Make MySQL reject illegal values rather than truncating or blanking them, see
-
# http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables
-
# If the user has provided another value for sql_mode, don't replace it.
-
unless variables.has_key?('sql_mode')
-
variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
-
end
-
-
# NAMES does not have an equals sign, see
-
# http://dev.mysql.com/doc/refman/5.0/en/set-statement.html#id944430
-
# (trailing comma because variable_assignments will always have content)
-
if @config[:encoding]
-
encoding = "NAMES #{@config[:encoding]}"
-
encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
-
encoding << ", "
-
end
-
-
# Gather up all of the SET variables...
-
variable_assignments = variables.map do |k, v|
-
if v == ':default' || v == :default
-
"@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
-
elsif !v.nil?
-
"@@SESSION.#{k} = #{quote(v)}"
-
end
-
# or else nil; compact to clear nils out
-
end.compact.join(', ')
-
-
# ...and send them all in one query
-
@connection.query "SET #{encoding} #{variable_assignments}"
-
end
-
-
1
def extract_foreign_key_action(structure, name, action) # :nodoc:
-
if structure =~ /CONSTRAINT #{quote_column_name(name)} FOREIGN KEY .* REFERENCES .* ON #{action} (CASCADE|SET NULL|RESTRICT)/
-
case $1
-
when 'CASCADE'; :cascade
-
when 'SET NULL'; :nullify
-
end
-
end
-
end
-
-
1
class MysqlDateTime < Type::DateTime # :nodoc:
-
1
private
-
-
1
def has_precision?
-
precision || 0
-
end
-
end
-
-
1
class MysqlString < Type::String # :nodoc:
-
1
def type_cast_for_database(value)
-
case value
-
when true then "1"
-
when false then "0"
-
else super
-
end
-
end
-
-
1
private
-
-
1
def cast_value(value)
-
case value
-
when true then "1"
-
when false then "0"
-
else super
-
end
-
end
-
end
-
end
-
end
-
end
-
1
require 'set'
-
-
1
module ActiveRecord
-
# :stopdoc:
-
1
module ConnectionAdapters
-
# An abstract definition of a column in a table.
-
1
class Column
-
1
TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON'].to_set
-
1
FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].to_set
-
-
1
module Format
-
1
ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
-
1
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
-
end
-
-
1
attr_reader :name, :cast_type, :null, :sql_type, :default, :default_function
-
-
1
delegate :type, :precision, :scale, :limit, :klass, :accessor,
-
:text?, :number?, :binary?, :changed?,
-
:type_cast_from_user, :type_cast_from_database, :type_cast_for_database,
-
:type_cast_for_schema,
-
to: :cast_type
-
-
# Instantiates a new column in the table.
-
#
-
# +name+ is the column's name, such as <tt>supplier_id</tt> in <tt>supplier_id int(11)</tt>.
-
# +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
-
# +cast_type+ is the object used for type casting and type information.
-
# +sql_type+ is used to extract the column's length, if necessary. For example +60+ in
-
# <tt>company_name varchar(60)</tt>.
-
# It will be mapped to one of the standard Rails SQL types in the <tt>type</tt> attribute.
-
# +null+ determines if this column allows +NULL+ values.
-
1
def initialize(name, default, cast_type, sql_type = nil, null = true)
-
35
@name = name.freeze
-
35
@cast_type = cast_type
-
35
@sql_type = sql_type
-
35
@null = null
-
35
@default = default
-
35
@default_function = nil
-
end
-
-
1
def has_default?
-
!default.nil?
-
end
-
-
# Returns the human name of the column name.
-
#
-
# ===== Examples
-
# Column.new('sales_stage', ...).human_name # => 'Sales stage'
-
1
def human_name
-
Base.human_attribute_name(@name)
-
end
-
-
1
def with_type(type)
-
35
dup.tap do |clone|
-
35
clone.instance_variable_set('@cast_type', type)
-
end
-
end
-
-
1
def ==(other)
-
other.name == name &&
-
other.default == default &&
-
other.cast_type == cast_type &&
-
other.sql_type == sql_type &&
-
other.null == null &&
-
other.default_function == default_function
-
end
-
1
alias :eql? :==
-
-
1
def hash
-
attributes_for_hash.hash
-
end
-
-
1
private
-
-
1
def attributes_for_hash
-
[self.class, name, default, cast_type, sql_type, null, default_function]
-
end
-
end
-
end
-
# :startdoc:
-
end
-
1
require 'uri'
-
1
require 'active_support/core_ext/string/filters'
-
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
class ConnectionSpecification #:nodoc:
-
1
attr_reader :config, :adapter_method
-
-
1
def initialize(config, adapter_method)
-
2
@config, @adapter_method = config, adapter_method
-
end
-
-
1
def initialize_dup(original)
-
@config = original.config.dup
-
end
-
-
# Expands a connection string into a hash.
-
1
class ConnectionUrlResolver # :nodoc:
-
-
# == Example
-
#
-
# url = "postgresql://foo:bar@localhost:9000/foo_test?pool=5&timeout=3000"
-
# ConnectionUrlResolver.new(url).to_hash
-
# # => {
-
# "adapter" => "postgresql",
-
# "host" => "localhost",
-
# "port" => 9000,
-
# "database" => "foo_test",
-
# "username" => "foo",
-
# "password" => "bar",
-
# "pool" => "5",
-
# "timeout" => "3000"
-
# }
-
1
def initialize(url)
-
raise "Database URL cannot be empty" if url.blank?
-
@uri = uri_parser.parse(url)
-
@adapter = @uri.scheme.tr('-', '_')
-
@adapter = "postgresql" if @adapter == "postgres"
-
-
if @uri.opaque
-
@uri.opaque, @query = @uri.opaque.split('?', 2)
-
else
-
@query = @uri.query
-
end
-
end
-
-
# Converts the given URL to a full connection hash.
-
1
def to_hash
-
config = raw_config.reject { |_,value| value.blank? }
-
config.map { |key,value| config[key] = uri_parser.unescape(value) if value.is_a? String }
-
config
-
end
-
-
1
private
-
-
1
def uri
-
@uri
-
end
-
-
1
def uri_parser
-
@uri_parser ||= URI::Parser.new
-
end
-
-
# Converts the query parameters of the URI into a hash.
-
#
-
# "localhost?pool=5&reaping_frequency=2"
-
# # => { "pool" => "5", "reaping_frequency" => "2" }
-
#
-
# returns empty hash if no query present.
-
#
-
# "localhost"
-
# # => {}
-
1
def query_hash
-
Hash[(@query || '').split("&").map { |pair| pair.split("=") }]
-
end
-
-
1
def raw_config
-
if uri.opaque
-
query_hash.merge({
-
"adapter" => @adapter,
-
"database" => uri.opaque })
-
else
-
query_hash.merge({
-
"adapter" => @adapter,
-
"username" => uri.user,
-
"password" => uri.password,
-
"port" => uri.port,
-
"database" => database_from_path,
-
"host" => uri.hostname })
-
end
-
end
-
-
# Returns name of the database.
-
1
def database_from_path
-
if @adapter == 'sqlite3'
-
# 'sqlite3:/foo' is absolute, because that makes sense. The
-
# corresponding relative version, 'sqlite3:foo', is handled
-
# elsewhere, as an "opaque".
-
-
uri.path
-
else
-
# Only SQLite uses a filename as the "database" name; for
-
# anything else, a leading slash would be silly.
-
-
uri.path.sub(%r{^/}, "")
-
end
-
end
-
end
-
-
##
-
# Builds a ConnectionSpecification from user input.
-
1
class Resolver # :nodoc:
-
1
attr_reader :configurations
-
-
# Accepts a hash two layers deep, keys on the first layer represent
-
# environments such as "production". Keys must be strings.
-
1
def initialize(configurations)
-
4
@configurations = configurations
-
end
-
-
# Returns a hash with database connection information.
-
#
-
# == Examples
-
#
-
# Full hash Configuration.
-
#
-
# configurations = { "production" => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } }
-
# Resolver.new(configurations).resolve(:production)
-
# # => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3"}
-
#
-
# Initialized with URL configuration strings.
-
#
-
# configurations = { "production" => "postgresql://localhost/foo" }
-
# Resolver.new(configurations).resolve(:production)
-
# # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
-
#
-
1
def resolve(config)
-
7
if config
-
7
resolve_connection config
-
elsif env = ActiveRecord::ConnectionHandling::RAILS_ENV.call
-
resolve_symbol_connection env.to_sym
-
else
-
raise AdapterNotSpecified
-
end
-
end
-
-
# Expands each key in @configurations hash into fully resolved hash
-
1
def resolve_all
-
2
config = configurations.dup
-
2
config.each do |key, value|
-
5
config[key] = resolve(value) if value
-
end
-
2
config
-
end
-
-
# Returns an instance of ConnectionSpecification for a given adapter.
-
# Accepts a hash one layer deep that contains all connection information.
-
#
-
# == Example
-
#
-
# config = { "production" => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } }
-
# spec = Resolver.new(config).spec(:production)
-
# spec.adapter_method
-
# # => "sqlite3_connection"
-
# spec.config
-
# # => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" }
-
#
-
1
def spec(config)
-
2
spec = resolve(config).symbolize_keys
-
-
2
raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter)
-
-
2
path_to_adapter = "active_record/connection_adapters/#{spec[:adapter]}_adapter"
-
2
begin
-
2
require path_to_adapter
-
rescue Gem::LoadError => e
-
raise Gem::LoadError, "Specified '#{spec[:adapter]}' for database adapter, but the gem is not loaded. Add `gem '#{e.name}'` to your Gemfile (and ensure its version is at the minimum required by ActiveRecord)."
-
rescue LoadError => e
-
raise LoadError, "Could not load '#{path_to_adapter}'. Make sure that the adapter in config/database.yml is valid. If you use an adapter other than 'mysql', 'mysql2', 'postgresql' or 'sqlite3' add the necessary adapter gem to the Gemfile.", e.backtrace
-
end
-
-
2
adapter_method = "#{spec[:adapter]}_connection"
-
2
ConnectionSpecification.new(spec, adapter_method)
-
end
-
-
1
private
-
-
# Returns fully resolved connection, accepts hash, string or symbol.
-
# Always returns a hash.
-
#
-
# == Examples
-
#
-
# Symbol representing current environment.
-
#
-
# Resolver.new("production" => {}).resolve_connection(:production)
-
# # => {}
-
#
-
# One layer deep hash of connection values.
-
#
-
# Resolver.new({}).resolve_connection("adapter" => "sqlite3")
-
# # => { "adapter" => "sqlite3" }
-
#
-
# Connection URL.
-
#
-
# Resolver.new({}).resolve_connection("postgresql://localhost/foo")
-
# # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
-
#
-
1
def resolve_connection(spec)
-
8
case spec
-
when Symbol
-
1
resolve_symbol_connection spec
-
when String
-
resolve_string_connection spec
-
when Hash
-
7
resolve_hash_connection spec
-
end
-
end
-
-
1
def resolve_string_connection(spec)
-
# Rails has historically accepted a string to mean either
-
# an environment key or a URL spec, so we have deprecated
-
# this ambiguous behaviour and in the future this function
-
# can be removed in favor of resolve_url_connection.
-
if configurations.key?(spec) || spec !~ /:/
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
Passing a string to ActiveRecord::Base.establish_connection for a
-
configuration lookup is deprecated, please pass a symbol
-
(#{spec.to_sym.inspect}) instead.
-
MSG
-
-
resolve_symbol_connection(spec)
-
else
-
resolve_url_connection(spec)
-
end
-
end
-
-
# Takes the environment such as +:production+ or +:development+.
-
# This requires that the @configurations was initialized with a key that
-
# matches.
-
#
-
# Resolver.new("production" => {}).resolve_symbol_connection(:production)
-
# # => {}
-
#
-
1
def resolve_symbol_connection(spec)
-
1
if config = configurations[spec.to_s]
-
1
resolve_connection(config)
-
else
-
raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available: #{configurations.keys.inspect}")
-
end
-
end
-
-
# Accepts a hash. Expands the "url" key that contains a
-
# URL database connection to a full connection
-
# hash and merges with the rest of the hash.
-
# Connection details inside of the "url" key win any merge conflicts
-
1
def resolve_hash_connection(spec)
-
7
if spec["url"] && spec["url"] !~ /^jdbc:/
-
connection_hash = resolve_url_connection(spec.delete("url"))
-
spec.merge!(connection_hash)
-
end
-
7
spec
-
end
-
-
# Takes a connection URL.
-
#
-
# Resolver.new({}).resolve_url_connection("postgresql://localhost/foo")
-
# # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
-
#
-
1
def resolve_url_connection(url)
-
ConnectionUrlResolver.new(url).to_hash
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_record/connection_adapters/abstract_mysql_adapter'
-
-
1
gem 'mysql2', '>= 0.3.13', '< 0.5'
-
require 'mysql2'
-
-
module ActiveRecord
-
module ConnectionHandling # :nodoc:
-
# Establishes a connection to the database that's used by all Active Record objects.
-
def mysql2_connection(config)
-
config = config.symbolize_keys
-
-
config[:username] = 'root' if config[:username].nil?
-
-
if Mysql2::Client.const_defined? :FOUND_ROWS
-
config[:flags] = Mysql2::Client::FOUND_ROWS
-
end
-
-
client = Mysql2::Client.new(config)
-
options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0]
-
ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config)
-
rescue Mysql2::Error => error
-
if error.message.include?("Unknown database")
-
raise ActiveRecord::NoDatabaseError.new(error.message, error)
-
else
-
raise
-
end
-
end
-
end
-
-
module ConnectionAdapters
-
class Mysql2Adapter < AbstractMysqlAdapter
-
ADAPTER_NAME = 'Mysql2'.freeze
-
-
def initialize(connection, logger, connection_options, config)
-
super
-
@prepared_statements = false
-
configure_connection
-
end
-
-
MAX_INDEX_LENGTH_FOR_UTF8MB4 = 191
-
def initialize_schema_migrations_table
-
if charset == 'utf8mb4'
-
ActiveRecord::SchemaMigration.create_table(MAX_INDEX_LENGTH_FOR_UTF8MB4)
-
else
-
ActiveRecord::SchemaMigration.create_table
-
end
-
end
-
-
def supports_explain?
-
true
-
end
-
-
# HELPER METHODS ===========================================
-
-
def each_hash(result) # :nodoc:
-
if block_given?
-
result.each(:as => :hash, :symbolize_keys => true) do |row|
-
yield row
-
end
-
else
-
to_enum(:each_hash, result)
-
end
-
end
-
-
def error_number(exception)
-
exception.error_number if exception.respond_to?(:error_number)
-
end
-
-
#--
-
# QUOTING ==================================================
-
#++
-
-
def quote_string(string)
-
@connection.escape(string)
-
end
-
-
def quoted_date(value)
-
if value.acts_like?(:time) && value.respond_to?(:usec)
-
"#{super}.#{sprintf("%06d", value.usec)}"
-
else
-
super
-
end
-
end
-
-
#--
-
# CONNECTION MANAGEMENT ====================================
-
#++
-
-
def active?
-
return false unless @connection
-
@connection.ping
-
end
-
-
def reconnect!
-
super
-
disconnect!
-
connect
-
end
-
alias :reset! :reconnect!
-
-
# Disconnects from the database if already connected.
-
# Otherwise, this method does nothing.
-
def disconnect!
-
super
-
unless @connection.nil?
-
@connection.close
-
@connection = nil
-
end
-
end
-
-
#--
-
# DATABASE STATEMENTS ======================================
-
#++
-
-
def explain(arel, binds = [])
-
sql = "EXPLAIN #{to_sql(arel, binds.dup)}"
-
start = Time.now
-
result = exec_query(sql, 'EXPLAIN', binds)
-
elapsed = Time.now - start
-
-
ExplainPrettyPrinter.new.pp(result, elapsed)
-
end
-
-
class ExplainPrettyPrinter # :nodoc:
-
# Pretty prints the result of a EXPLAIN in a way that resembles the output of the
-
# MySQL shell:
-
#
-
# +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
-
# | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
-
# +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
-
# | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
-
# | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
-
# +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
-
# 2 rows in set (0.00 sec)
-
#
-
# This is an exercise in Ruby hyperrealism :).
-
def pp(result, elapsed)
-
widths = compute_column_widths(result)
-
separator = build_separator(widths)
-
-
pp = []
-
-
pp << separator
-
pp << build_cells(result.columns, widths)
-
pp << separator
-
-
result.rows.each do |row|
-
pp << build_cells(row, widths)
-
end
-
-
pp << separator
-
pp << build_footer(result.rows.length, elapsed)
-
-
pp.join("\n") + "\n"
-
end
-
-
private
-
-
def compute_column_widths(result)
-
[].tap do |widths|
-
result.columns.each_with_index do |column, i|
-
cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s}
-
widths << cells_in_column.map(&:length).max
-
end
-
end
-
end
-
-
def build_separator(widths)
-
padding = 1
-
'+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+'
-
end
-
-
def build_cells(items, widths)
-
cells = []
-
items.each_with_index do |item, i|
-
item = 'NULL' if item.nil?
-
justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust'
-
cells << item.to_s.send(justifier, widths[i])
-
end
-
'| ' + cells.join(' | ') + ' |'
-
end
-
-
def build_footer(nrows, elapsed)
-
rows_label = nrows == 1 ? 'row' : 'rows'
-
"#{nrows} #{rows_label} in set (%.2f sec)" % elapsed
-
end
-
end
-
-
# FIXME: re-enable the following once a "better" query_cache solution is in core
-
#
-
# The overrides below perform much better than the originals in AbstractAdapter
-
# because we're able to take advantage of mysql2's lazy-loading capabilities
-
#
-
# # Returns a record hash with the column names as keys and column values
-
# # as values.
-
# def select_one(sql, name = nil)
-
# result = execute(sql, name)
-
# result.each(as: :hash) do |r|
-
# return r
-
# end
-
# end
-
#
-
# # Returns a single value from a record
-
# def select_value(sql, name = nil)
-
# result = execute(sql, name)
-
# if first = result.first
-
# first.first
-
# end
-
# end
-
#
-
# # Returns an array of the values of the first column in a select:
-
# # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
-
# def select_values(sql, name = nil)
-
# execute(sql, name).map { |row| row.first }
-
# end
-
-
# Returns an array of arrays containing the field values.
-
# Order is the same as that returned by +columns+.
-
def select_rows(sql, name = nil, binds = [])
-
execute(sql, name).to_a
-
end
-
-
# Executes the SQL statement in the context of this connection.
-
def execute(sql, name = nil)
-
if @connection
-
# make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
-
# made since we established the connection
-
@connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
-
end
-
-
super
-
end
-
-
def exec_query(sql, name = 'SQL', binds = [])
-
result = execute(sql, name)
-
ActiveRecord::Result.new(result.fields, result.to_a)
-
end
-
-
alias exec_without_stmt exec_query
-
-
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
-
super
-
id_value || @connection.last_id
-
end
-
alias :create :insert_sql
-
-
def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
-
execute to_sql(sql, binds), name
-
end
-
-
def exec_delete(sql, name, binds)
-
execute to_sql(sql, binds), name
-
@connection.affected_rows
-
end
-
alias :exec_update :exec_delete
-
-
def last_inserted_id(result)
-
@connection.last_id
-
end
-
-
private
-
-
def connect
-
@connection = Mysql2::Client.new(@config)
-
configure_connection
-
end
-
-
def configure_connection
-
@connection.query_options.merge!(:as => :array)
-
super
-
end
-
-
def full_version
-
@full_version ||= @connection.server_info[:version]
-
end
-
-
def set_field_encoding field_name
-
field_name
-
end
-
end
-
end
-
end
-
1
require 'active_record/connection_adapters/abstract_mysql_adapter'
-
1
require 'active_record/connection_adapters/statement_pool'
-
1
require 'active_support/core_ext/hash/keys'
-
-
1
gem 'mysql', '~> 2.9'
-
require 'mysql'
-
-
class Mysql
-
class Time
-
def to_date
-
Date.new(year, month, day)
-
end
-
end
-
class Stmt; include Enumerable end
-
class Result; include Enumerable end
-
end
-
-
module ActiveRecord
-
module ConnectionHandling # :nodoc:
-
# Establishes a connection to the database that's used by all Active Record objects.
-
def mysql_connection(config)
-
config = config.symbolize_keys
-
host = config[:host]
-
port = config[:port]
-
socket = config[:socket]
-
username = config[:username] ? config[:username].to_s : 'root'
-
password = config[:password].to_s
-
database = config[:database]
-
-
mysql = Mysql.init
-
mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslca] || config[:sslkey]
-
-
default_flags = Mysql.const_defined?(:CLIENT_MULTI_RESULTS) ? Mysql::CLIENT_MULTI_RESULTS : 0
-
default_flags |= Mysql::CLIENT_FOUND_ROWS if Mysql.const_defined?(:CLIENT_FOUND_ROWS)
-
options = [host, username, password, database, port, socket, default_flags]
-
ConnectionAdapters::MysqlAdapter.new(mysql, logger, options, config)
-
rescue Mysql::Error => error
-
if error.message.include?("Unknown database")
-
raise ActiveRecord::NoDatabaseError.new(error.message, error)
-
else
-
raise
-
end
-
end
-
end
-
-
module ConnectionAdapters
-
# The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
-
# the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/).
-
#
-
# Options:
-
#
-
# * <tt>:host</tt> - Defaults to "localhost".
-
# * <tt>:port</tt> - Defaults to 3306.
-
# * <tt>:socket</tt> - Defaults to "/tmp/mysql.sock".
-
# * <tt>:username</tt> - Defaults to "root"
-
# * <tt>:password</tt> - Defaults to nothing.
-
# * <tt>:database</tt> - The name of the database. No default, must be provided.
-
# * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
-
# * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
-
# * <tt>:strict</tt> - Defaults to true. Enable STRICT_ALL_TABLES. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/sql-mode.html)
-
# * <tt>:variables</tt> - (Optional) A hash session variables to send as <tt>SET @@SESSION.key = value</tt> on each database connection. Use the value +:default+ to set a variable to its DEFAULT value. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/set-statement.html).
-
# * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
-
# * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
-
# * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
-
# * <tt>:sslcapath</tt> - Necessary to use MySQL with an SSL connection.
-
# * <tt>:sslcipher</tt> - Necessary to use MySQL with an SSL connection.
-
#
-
class MysqlAdapter < AbstractMysqlAdapter
-
ADAPTER_NAME = 'MySQL'.freeze
-
-
class StatementPool < ConnectionAdapters::StatementPool
-
def initialize(connection, max = 1000)
-
super
-
@cache = Hash.new { |h,pid| h[pid] = {} }
-
end
-
-
def each(&block); cache.each(&block); end
-
def key?(key); cache.key?(key); end
-
def [](key); cache[key]; end
-
def length; cache.length; end
-
def delete(key); cache.delete(key); end
-
-
def []=(sql, key)
-
while @max <= cache.size
-
cache.shift.last[:stmt].close
-
end
-
cache[sql] = key
-
end
-
-
def clear
-
cache.each_value do |hash|
-
hash[:stmt].close
-
end
-
cache.clear
-
end
-
-
private
-
def cache
-
@cache[Process.pid]
-
end
-
end
-
-
def initialize(connection, logger, connection_options, config)
-
super
-
@statements = StatementPool.new(@connection,
-
self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
-
@client_encoding = nil
-
connect
-
end
-
-
# Returns true, since this connection adapter supports prepared statement
-
# caching.
-
def supports_statement_cache?
-
true
-
end
-
-
# HELPER METHODS ===========================================
-
-
def each_hash(result) # :nodoc:
-
if block_given?
-
result.each_hash do |row|
-
row.symbolize_keys!
-
yield row
-
end
-
else
-
to_enum(:each_hash, result)
-
end
-
end
-
-
def error_number(exception) # :nodoc:
-
exception.errno if exception.respond_to?(:errno)
-
end
-
-
# QUOTING ==================================================
-
-
def quote_string(string) #:nodoc:
-
@connection.quote(string)
-
end
-
-
#--
-
# CONNECTION MANAGEMENT ====================================
-
#++
-
-
def active?
-
if @connection.respond_to?(:stat)
-
@connection.stat
-
else
-
@connection.query 'select 1'
-
end
-
-
# mysql-ruby doesn't raise an exception when stat fails.
-
if @connection.respond_to?(:errno)
-
@connection.errno.zero?
-
else
-
true
-
end
-
rescue Mysql::Error
-
false
-
end
-
-
def reconnect!
-
super
-
disconnect!
-
connect
-
end
-
-
# Disconnects from the database if already connected. Otherwise, this
-
# method does nothing.
-
def disconnect!
-
super
-
@connection.close rescue nil
-
end
-
-
def reset!
-
if @connection.respond_to?(:change_user)
-
# See http://bugs.mysql.com/bug.php?id=33540 -- the workaround way to
-
# reset the connection is to change the user to the same user.
-
@connection.change_user(@config[:username], @config[:password], @config[:database])
-
configure_connection
-
end
-
end
-
-
#--
-
# DATABASE STATEMENTS ======================================
-
#++
-
-
def select_rows(sql, name = nil, binds = [])
-
@connection.query_with_result = true
-
rows = exec_query(sql, name, binds).rows
-
@connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
-
rows
-
end
-
-
# Clears the prepared statements cache.
-
def clear_cache!
-
super
-
@statements.clear
-
end
-
-
# Taken from here:
-
# https://github.com/tmtm/ruby-mysql/blob/master/lib/mysql/charset.rb
-
# Author: TOMITA Masahiro <tommy@tmtm.org>
-
ENCODINGS = {
-
"armscii8" => nil,
-
"ascii" => Encoding::US_ASCII,
-
"big5" => Encoding::Big5,
-
"binary" => Encoding::ASCII_8BIT,
-
"cp1250" => Encoding::Windows_1250,
-
"cp1251" => Encoding::Windows_1251,
-
"cp1256" => Encoding::Windows_1256,
-
"cp1257" => Encoding::Windows_1257,
-
"cp850" => Encoding::CP850,
-
"cp852" => Encoding::CP852,
-
"cp866" => Encoding::IBM866,
-
"cp932" => Encoding::Windows_31J,
-
"dec8" => nil,
-
"eucjpms" => Encoding::EucJP_ms,
-
"euckr" => Encoding::EUC_KR,
-
"gb2312" => Encoding::EUC_CN,
-
"gbk" => Encoding::GBK,
-
"geostd8" => nil,
-
"greek" => Encoding::ISO_8859_7,
-
"hebrew" => Encoding::ISO_8859_8,
-
"hp8" => nil,
-
"keybcs2" => nil,
-
"koi8r" => Encoding::KOI8_R,
-
"koi8u" => Encoding::KOI8_U,
-
"latin1" => Encoding::ISO_8859_1,
-
"latin2" => Encoding::ISO_8859_2,
-
"latin5" => Encoding::ISO_8859_9,
-
"latin7" => Encoding::ISO_8859_13,
-
"macce" => Encoding::MacCentEuro,
-
"macroman" => Encoding::MacRoman,
-
"sjis" => Encoding::SHIFT_JIS,
-
"swe7" => nil,
-
"tis620" => Encoding::TIS_620,
-
"ucs2" => Encoding::UTF_16BE,
-
"ujis" => Encoding::EucJP_ms,
-
"utf8" => Encoding::UTF_8,
-
"utf8mb4" => Encoding::UTF_8,
-
}
-
-
# Get the client encoding for this database
-
def client_encoding
-
return @client_encoding if @client_encoding
-
-
result = exec_query(
-
"SHOW VARIABLES WHERE Variable_name = 'character_set_client'",
-
'SCHEMA')
-
@client_encoding = ENCODINGS[result.rows.last.last]
-
end
-
-
def exec_query(sql, name = 'SQL', binds = [])
-
if without_prepared_statement?(binds)
-
result_set, affected_rows = exec_without_stmt(sql, name)
-
else
-
result_set, affected_rows = exec_stmt(sql, name, binds)
-
end
-
-
yield affected_rows if block_given?
-
-
result_set
-
end
-
-
def last_inserted_id(result)
-
@connection.insert_id
-
end
-
-
module Fields # :nodoc:
-
class DateTime < Type::DateTime # :nodoc:
-
def cast_value(value)
-
if Mysql::Time === value
-
new_time(
-
value.year,
-
value.month,
-
value.day,
-
value.hour,
-
value.minute,
-
value.second,
-
value.second_part)
-
else
-
super
-
end
-
end
-
end
-
-
class Time < Type::Time # :nodoc:
-
def cast_value(value)
-
if Mysql::Time === value
-
new_time(
-
2000,
-
01,
-
01,
-
value.hour,
-
value.minute,
-
value.second,
-
value.second_part)
-
else
-
super
-
end
-
end
-
end
-
-
class << self
-
TYPES = Type::HashLookupTypeMap.new # :nodoc:
-
-
delegate :register_type, :alias_type, to: :TYPES
-
-
def find_type(field)
-
if field.type == Mysql::Field::TYPE_TINY && field.length > 1
-
TYPES.lookup(Mysql::Field::TYPE_LONG)
-
else
-
TYPES.lookup(field.type)
-
end
-
end
-
end
-
-
register_type Mysql::Field::TYPE_TINY, Type::Boolean.new
-
register_type Mysql::Field::TYPE_LONG, Type::Integer.new
-
alias_type Mysql::Field::TYPE_LONGLONG, Mysql::Field::TYPE_LONG
-
alias_type Mysql::Field::TYPE_NEWDECIMAL, Mysql::Field::TYPE_LONG
-
-
register_type Mysql::Field::TYPE_DATE, Type::Date.new
-
register_type Mysql::Field::TYPE_DATETIME, Fields::DateTime.new
-
register_type Mysql::Field::TYPE_TIME, Fields::Time.new
-
register_type Mysql::Field::TYPE_FLOAT, Type::Float.new
-
end
-
-
def initialize_type_map(m) # :nodoc:
-
super
-
m.register_type %r(datetime)i, Fields::DateTime.new
-
m.register_type %r(time)i, Fields::Time.new
-
end
-
-
def exec_without_stmt(sql, name = 'SQL') # :nodoc:
-
# Some queries, like SHOW CREATE TABLE don't work through the prepared
-
# statement API. For those queries, we need to use this method. :'(
-
log(sql, name) do
-
result = @connection.query(sql)
-
affected_rows = @connection.affected_rows
-
-
if result
-
types = {}
-
fields = []
-
result.fetch_fields.each { |field|
-
field_name = field.name
-
fields << field_name
-
-
if field.decimals > 0
-
types[field_name] = Type::Decimal.new
-
else
-
types[field_name] = Fields.find_type field
-
end
-
}
-
-
result_set = ActiveRecord::Result.new(fields, result.to_a, types)
-
result.free
-
else
-
result_set = ActiveRecord::Result.new([], [])
-
end
-
-
[result_set, affected_rows]
-
end
-
end
-
-
def execute_and_free(sql, name = nil) # :nodoc:
-
result = execute(sql, name)
-
ret = yield result
-
result.free
-
ret
-
end
-
-
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
-
super sql, name
-
id_value || @connection.insert_id
-
end
-
alias :create :insert_sql
-
-
def exec_delete(sql, name, binds) # :nodoc:
-
affected_rows = 0
-
-
exec_query(sql, name, binds) do |n|
-
affected_rows = n
-
end
-
-
affected_rows
-
end
-
alias :exec_update :exec_delete
-
-
def begin_db_transaction #:nodoc:
-
exec_query "BEGIN"
-
end
-
-
private
-
-
def exec_stmt(sql, name, binds)
-
cache = {}
-
type_casted_binds = binds.map { |col, val|
-
[col, type_cast(val, col)]
-
}
-
-
log(sql, name, type_casted_binds) do
-
if binds.empty?
-
stmt = @connection.prepare(sql)
-
else
-
cache = @statements[sql] ||= {
-
:stmt => @connection.prepare(sql)
-
}
-
stmt = cache[:stmt]
-
end
-
-
begin
-
stmt.execute(*type_casted_binds.map { |_, val| val })
-
rescue Mysql::Error => e
-
# Older versions of MySQL leave the prepared statement in a bad
-
# place when an error occurs. To support older MySQL versions, we
-
# need to close the statement and delete the statement from the
-
# cache.
-
stmt.close
-
@statements.delete sql
-
raise e
-
end
-
-
cols = nil
-
if metadata = stmt.result_metadata
-
cols = cache[:cols] ||= metadata.fetch_fields.map { |field|
-
field.name
-
}
-
metadata.free
-
end
-
-
result_set = ActiveRecord::Result.new(cols, stmt.to_a) if cols
-
affected_rows = stmt.affected_rows
-
-
stmt.free_result
-
stmt.close if binds.empty?
-
-
[result_set, affected_rows]
-
end
-
end
-
-
def connect
-
encoding = @config[:encoding]
-
if encoding
-
@connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
-
end
-
-
if @config[:sslca] || @config[:sslkey]
-
@connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
-
end
-
-
@connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout]
-
@connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout]
-
@connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout]
-
-
@connection.real_connect(*@connection_options)
-
-
# reconnect must be set after real_connect is called, because real_connect sets it to false internally
-
@connection.reconnect = !!@config[:reconnect] if @connection.respond_to?(:reconnect=)
-
-
configure_connection
-
end
-
-
# Many Rails applications monkey-patch a replacement of the configure_connection method
-
# and don't call 'super', so leave this here even though it looks superfluous.
-
def configure_connection
-
super
-
end
-
-
def select(sql, name = nil, binds = [])
-
@connection.query_with_result = true
-
rows = super
-
@connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
-
rows
-
end
-
-
# Returns the full version of the connected MySQL server.
-
def full_version
-
@full_version ||= @connection.server_info
-
end
-
-
def set_field_encoding field_name
-
field_name.force_encoding(client_encoding)
-
if internal_enc = Encoding.default_internal
-
field_name = field_name.encode!(internal_enc)
-
end
-
field_name
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module ArrayParser # :nodoc:
-
-
1
DOUBLE_QUOTE = '"'
-
1
BACKSLASH = "\\"
-
1
COMMA = ','
-
1
BRACKET_OPEN = '{'
-
1
BRACKET_CLOSE = '}'
-
-
1
def parse_pg_array(string) # :nodoc:
-
local_index = 0
-
array = []
-
while(local_index < string.length)
-
case string[local_index]
-
when BRACKET_OPEN
-
local_index,array = parse_array_contents(array, string, local_index + 1)
-
when BRACKET_CLOSE
-
return array
-
end
-
local_index += 1
-
end
-
-
array
-
end
-
-
1
private
-
-
1
def parse_array_contents(array, string, index)
-
is_escaping = false
-
is_quoted = false
-
was_quoted = false
-
current_item = ''
-
-
local_index = index
-
while local_index
-
token = string[local_index]
-
if is_escaping
-
current_item << token
-
is_escaping = false
-
else
-
if is_quoted
-
case token
-
when DOUBLE_QUOTE
-
is_quoted = false
-
was_quoted = true
-
when BACKSLASH
-
is_escaping = true
-
else
-
current_item << token
-
end
-
else
-
case token
-
when BACKSLASH
-
is_escaping = true
-
when COMMA
-
add_item_to_array(array, current_item, was_quoted)
-
current_item = ''
-
was_quoted = false
-
when DOUBLE_QUOTE
-
is_quoted = true
-
when BRACKET_OPEN
-
internal_items = []
-
local_index,internal_items = parse_array_contents(internal_items, string, local_index + 1)
-
array.push(internal_items)
-
when BRACKET_CLOSE
-
add_item_to_array(array, current_item, was_quoted)
-
return local_index,array
-
else
-
current_item << token
-
end
-
end
-
end
-
-
local_index += 1
-
end
-
return local_index,array
-
end
-
-
1
def add_item_to_array(array, current_item, quoted)
-
return if !quoted && current_item.length == 0
-
-
if !quoted && current_item == 'NULL'
-
array.push nil
-
else
-
array.push current_item
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
# PostgreSQL-specific extensions to column definitions in a table.
-
1
class PostgreSQLColumn < Column #:nodoc:
-
1
attr_accessor :array
-
-
1
def initialize(name, default, cast_type, sql_type = nil, null = true, default_function = nil)
-
if sql_type =~ /\[\]$/
-
@array = true
-
super(name, default, cast_type, sql_type[0..sql_type.length - 3], null)
-
else
-
@array = false
-
super(name, default, cast_type, sql_type, null)
-
end
-
-
@default_function = default_function
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module DatabaseStatements
-
1
def explain(arel, binds = [])
-
sql = "EXPLAIN #{to_sql(arel, binds)}"
-
ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
-
end
-
-
1
class ExplainPrettyPrinter # :nodoc:
-
# Pretty prints the result of a EXPLAIN in a way that resembles the output of the
-
# PostgreSQL shell:
-
#
-
# QUERY PLAN
-
# ------------------------------------------------------------------------------
-
# Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
-
# Join Filter: (posts.user_id = users.id)
-
# -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
-
# Index Cond: (id = 1)
-
# -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
-
# Filter: (posts.user_id = 1)
-
# (6 rows)
-
#
-
1
def pp(result)
-
header = result.columns.first
-
lines = result.rows.map(&:first)
-
-
# We add 2 because there's one char of padding at both sides, note
-
# the extra hyphens in the example above.
-
width = [header, *lines].map(&:length).max + 2
-
-
pp = []
-
-
pp << header.center(width).rstrip
-
pp << '-' * width
-
-
pp += lines.map {|line| " #{line}"}
-
-
nrows = result.rows.length
-
rows_label = nrows == 1 ? 'row' : 'rows'
-
pp << "(#{nrows} #{rows_label})"
-
-
pp.join("\n") + "\n"
-
end
-
end
-
-
1
def select_value(arel, name = nil, binds = [])
-
arel, binds = binds_from_relation arel, binds
-
sql = to_sql(arel, binds)
-
execute_and_clear(sql, name, binds) do |result|
-
result.getvalue(0, 0) if result.ntuples > 0 && result.nfields > 0
-
end
-
end
-
-
1
def select_values(arel, name = nil)
-
arel, binds = binds_from_relation arel, []
-
sql = to_sql(arel, binds)
-
execute_and_clear(sql, name, binds) do |result|
-
if result.nfields > 0
-
result.column_values(0)
-
else
-
[]
-
end
-
end
-
end
-
-
# Executes a SELECT query and returns an array of rows. Each row is an
-
# array of field values.
-
1
def select_rows(sql, name = nil, binds = [])
-
execute_and_clear(sql, name, binds) do |result|
-
result.values
-
end
-
end
-
-
# Executes an INSERT query and returns the new record's ID
-
1
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
-
unless pk
-
# Extract the table from the insert sql. Yuck.
-
table_ref = extract_table_ref_from_insert_sql(sql)
-
pk = primary_key(table_ref) if table_ref
-
end
-
-
if pk && use_insert_returning?
-
select_value("#{sql} RETURNING #{quote_column_name(pk)}")
-
elsif pk
-
super
-
last_insert_id_value(sequence_name || default_sequence_name(table_ref, pk))
-
else
-
super
-
end
-
end
-
-
1
def create
-
super.insert
-
end
-
-
# The internal PostgreSQL identifier of the money data type.
-
1
MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
-
# The internal PostgreSQL identifier of the BYTEA data type.
-
1
BYTEA_COLUMN_TYPE_OID = 17 #:nodoc:
-
-
# create a 2D array representing the result set
-
1
def result_as_array(res) #:nodoc:
-
# check if we have any binary column and if they need escaping
-
ftypes = Array.new(res.nfields) do |i|
-
[i, res.ftype(i)]
-
end
-
-
rows = res.values
-
return rows unless ftypes.any? { |_, x|
-
x == BYTEA_COLUMN_TYPE_OID || x == MONEY_COLUMN_TYPE_OID
-
}
-
-
typehash = ftypes.group_by { |_, type| type }
-
binaries = typehash[BYTEA_COLUMN_TYPE_OID] || []
-
monies = typehash[MONEY_COLUMN_TYPE_OID] || []
-
-
rows.each do |row|
-
# unescape string passed BYTEA field (OID == 17)
-
binaries.each do |index, _|
-
row[index] = unescape_bytea(row[index])
-
end
-
-
# If this is a money type column and there are any currency symbols,
-
# then strip them off. Indeed it would be prettier to do this in
-
# PostgreSQLColumn.string_to_decimal but would break form input
-
# fields that call value_before_type_cast.
-
monies.each do |index, _|
-
data = row[index]
-
# Because money output is formatted according to the locale, there are two
-
# cases to consider (note the decimal separators):
-
# (1) $12,345,678.12
-
# (2) $12.345.678,12
-
case data
-
when /^-?\D+[\d,]+\.\d{2}$/ # (1)
-
data.gsub!(/[^-\d.]/, '')
-
when /^-?\D+[\d.]+,\d{2}$/ # (2)
-
data.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
-
end
-
end
-
end
-
end
-
-
# Queries the database and returns the results in an Array-like object
-
1
def query(sql, name = nil) #:nodoc:
-
log(sql, name) do
-
result_as_array @connection.async_exec(sql)
-
end
-
end
-
-
# Executes an SQL statement, returning a PGresult object on success
-
# or raising a PGError exception otherwise.
-
1
def execute(sql, name = nil)
-
log(sql, name) do
-
@connection.async_exec(sql)
-
end
-
end
-
-
1
def exec_query(sql, name = 'SQL', binds = [])
-
execute_and_clear(sql, name, binds) do |result|
-
types = {}
-
fields = result.fields
-
fields.each_with_index do |fname, i|
-
ftype = result.ftype i
-
fmod = result.fmod i
-
types[fname] = get_oid_type(ftype, fmod, fname)
-
end
-
ActiveRecord::Result.new(fields, result.values, types)
-
end
-
end
-
-
1
def exec_delete(sql, name = 'SQL', binds = [])
-
execute_and_clear(sql, name, binds) {|result| result.cmd_tuples }
-
end
-
1
alias :exec_update :exec_delete
-
-
1
def sql_for_insert(sql, pk, id_value, sequence_name, binds)
-
unless pk
-
# Extract the table from the insert sql. Yuck.
-
table_ref = extract_table_ref_from_insert_sql(sql)
-
pk = primary_key(table_ref) if table_ref
-
end
-
-
if pk && use_insert_returning?
-
sql = "#{sql} RETURNING #{quote_column_name(pk)}"
-
end
-
-
[sql, binds]
-
end
-
-
1
def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
-
val = exec_query(sql, name, binds)
-
if !use_insert_returning? && pk
-
unless sequence_name
-
table_ref = extract_table_ref_from_insert_sql(sql)
-
sequence_name = default_sequence_name(table_ref, pk)
-
return val unless sequence_name
-
end
-
last_insert_id_result(sequence_name)
-
else
-
val
-
end
-
end
-
-
# Executes an UPDATE query and returns the number of affected tuples.
-
1
def update_sql(sql, name = nil)
-
super.cmd_tuples
-
end
-
-
# Begins a transaction.
-
1
def begin_db_transaction
-
execute "BEGIN"
-
end
-
-
1
def begin_isolated_db_transaction(isolation)
-
begin_db_transaction
-
execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
-
end
-
-
# Commits a transaction.
-
1
def commit_db_transaction
-
execute "COMMIT"
-
end
-
-
# Aborts a transaction.
-
1
def exec_rollback_db_transaction
-
execute "ROLLBACK"
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_record/connection_adapters/postgresql/oid/infinity'
-
-
1
require 'active_record/connection_adapters/postgresql/oid/array'
-
1
require 'active_record/connection_adapters/postgresql/oid/bit'
-
1
require 'active_record/connection_adapters/postgresql/oid/bit_varying'
-
1
require 'active_record/connection_adapters/postgresql/oid/bytea'
-
1
require 'active_record/connection_adapters/postgresql/oid/cidr'
-
1
require 'active_record/connection_adapters/postgresql/oid/date'
-
1
require 'active_record/connection_adapters/postgresql/oid/date_time'
-
1
require 'active_record/connection_adapters/postgresql/oid/decimal'
-
1
require 'active_record/connection_adapters/postgresql/oid/enum'
-
1
require 'active_record/connection_adapters/postgresql/oid/float'
-
1
require 'active_record/connection_adapters/postgresql/oid/hstore'
-
1
require 'active_record/connection_adapters/postgresql/oid/inet'
-
1
require 'active_record/connection_adapters/postgresql/oid/integer'
-
1
require 'active_record/connection_adapters/postgresql/oid/json'
-
1
require 'active_record/connection_adapters/postgresql/oid/jsonb'
-
1
require 'active_record/connection_adapters/postgresql/oid/money'
-
1
require 'active_record/connection_adapters/postgresql/oid/point'
-
1
require 'active_record/connection_adapters/postgresql/oid/range'
-
1
require 'active_record/connection_adapters/postgresql/oid/specialized_string'
-
1
require 'active_record/connection_adapters/postgresql/oid/time'
-
1
require 'active_record/connection_adapters/postgresql/oid/uuid'
-
1
require 'active_record/connection_adapters/postgresql/oid/vector'
-
1
require 'active_record/connection_adapters/postgresql/oid/xml'
-
-
1
require 'active_record/connection_adapters/postgresql/oid/type_map_initializer'
-
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module OID # :nodoc:
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module OID # :nodoc:
-
1
class Array < Type::Value # :nodoc:
-
1
include Type::Mutable
-
-
# Loads pg_array_parser if available. String parsing can be
-
# performed quicker by a native extension, which will not create
-
# a large amount of Ruby objects that will need to be garbage
-
# collected. pg_array_parser has a C and Java extension
-
1
begin
-
1
require 'pg_array_parser'
-
include PgArrayParser
-
rescue LoadError
-
1
require 'active_record/connection_adapters/postgresql/array_parser'
-
1
include PostgreSQL::ArrayParser
-
end
-
-
1
attr_reader :subtype, :delimiter
-
1
delegate :type, :limit, to: :subtype
-
-
1
def initialize(subtype, delimiter = ',')
-
@subtype = subtype
-
@delimiter = delimiter
-
end
-
-
1
def type_cast_from_database(value)
-
if value.is_a?(::String)
-
type_cast_array(parse_pg_array(value), :type_cast_from_database)
-
else
-
super
-
end
-
end
-
-
1
def type_cast_from_user(value)
-
if value.is_a?(::String)
-
value = parse_pg_array(value)
-
end
-
type_cast_array(value, :type_cast_from_user)
-
end
-
-
1
def type_cast_for_database(value)
-
if value.is_a?(::Array)
-
cast_value_for_database(value)
-
else
-
super
-
end
-
end
-
-
1
private
-
-
1
def type_cast_array(value, method)
-
if value.is_a?(::Array)
-
value.map { |item| type_cast_array(item, method) }
-
else
-
@subtype.public_send(method, value)
-
end
-
end
-
-
1
def cast_value_for_database(value)
-
if value.is_a?(::Array)
-
casted_values = value.map { |item| cast_value_for_database(item) }
-
"{#{casted_values.join(delimiter)}}"
-
else
-
quote_and_escape(subtype.type_cast_for_database(value))
-
end
-
end
-
-
1
ARRAY_ESCAPE = "\\" * 2 * 2 # escape the backslash twice for PG arrays
-
-
1
def quote_and_escape(value)
-
case value
-
when ::String
-
if string_requires_quoting?(value)
-
value = value.gsub(/\\/, ARRAY_ESCAPE)
-
value.gsub!(/"/,"\\\"")
-
%("#{value}")
-
else
-
value
-
end
-
when nil then "NULL"
-
else value
-
end
-
end
-
-
# See http://www.postgresql.org/docs/9.2/static/arrays.html#ARRAYS-IO
-
# for a list of all cases in which strings will be quoted.
-
1
def string_requires_quoting?(string)
-
string.empty? ||
-
string == "NULL" ||
-
string =~ /[\{\}"\\\s]/ ||
-
string.include?(delimiter)
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module OID # :nodoc:
-
1
class Bit < Type::Value # :nodoc:
-
1
def type
-
:bit
-
end
-
-
1
def type_cast(value)
-
if ::String === value
-
case value
-
when /^0x/i
-
value[2..-1].hex.to_s(2) # Hexadecimal notation
-
else
-
value # Bit-string notation
-
end
-
else
-
value
-
end
-
end
-
-
1
def type_cast_for_database(value)
-
Data.new(super) if value
-
end
-
-
1
class Data
-
1
def initialize(value)
-
@value = value
-
end
-
-
1
def to_s
-
value
-
end
-
-
1
def binary?
-
/\A[01]*\Z/ === value
-
end
-
-
1
def hex?
-
/\A[0-9A-F]*\Z/i === value
-
end
-
-
1
protected
-
-
1
attr_reader :value
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module OID # :nodoc:
-
1
class BitVarying < OID::Bit # :nodoc:
-
1
def type
-
:bit_varying
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module OID # :nodoc:
-
1
class Bytea < Type::Binary # :nodoc:
-
1
def type_cast_from_database(value)
-
return if value.nil?
-
return value.to_s if value.is_a?(Type::Binary::Data)
-
PGconn.unescape_bytea(super)
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module OID # :nodoc:
-
1
class Cidr < Type::Value # :nodoc:
-
1
def type
-
:cidr
-
end
-
-
1
def type_cast_for_schema(value)
-
subnet_mask = value.instance_variable_get(:@mask_addr)
-
-
# If the subnet mask is equal to /32, don't output it
-
if subnet_mask == (2**32 - 1)
-
"\"#{value}\""
-
else
-
"\"#{value}/#{subnet_mask.to_s(2).count('1')}\""
-
end
-
end
-
-
1
def type_cast_for_database(value)
-
if IPAddr === value
-
"#{value}/#{value.instance_variable_get(:@mask_addr).to_s(2).count('1')}"
-
else
-
value
-
end
-
end
-
-
1
def cast_value(value)
-
if value.nil?
-
nil
-
elsif String === value
-
begin
-
IPAddr.new(value)
-
rescue ArgumentError
-
nil
-
end
-
else
-
value
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module OID # :nodoc:
-
1
class Date < Type::Date # :nodoc:
-
1
include Infinity
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module OID # :nodoc:
-
1
class DateTime < Type::DateTime # :nodoc:
-
1
include Infinity
-
-
1
def type_cast_for_database(value)
-
if has_precision? && value.acts_like?(:time) && value.year <= 0
-
bce_year = format("%04d", -value.year + 1)
-
super.sub(/^-?\d+/, bce_year) + " BC"
-
else
-
super
-
end
-
end
-
-
1
def cast_value(value)
-
if value.is_a?(::String)
-
case value
-
when 'infinity' then ::Float::INFINITY
-
when '-infinity' then -::Float::INFINITY
-
when / BC$/
-
astronomical_year = format("%04d", -value[/^\d+/].to_i + 1)
-
super(value.sub(/ BC$/, "").sub(/^\d+/, astronomical_year))
-
else
-
super
-
end
-
else
-
value
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module OID # :nodoc:
-
1
class Decimal < Type::Decimal # :nodoc:
-
1
def infinity(options = {})
-
BigDecimal.new("Infinity") * (options[:negative] ? -1 : 1)
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module OID # :nodoc:
-
1
class Enum < Type::Value # :nodoc:
-
1
def type
-
:enum
-
end
-
-
1
private
-
-
1
def cast_value(value)
-
value.to_s
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module OID # :nodoc:
-
1
class Float < Type::Float # :nodoc:
-
1
include Infinity
-
-
1
def cast_value(value)
-
case value
-
when ::Float then value
-
when 'Infinity' then ::Float::INFINITY
-
when '-Infinity' then -::Float::INFINITY
-
when 'NaN' then ::Float::NAN
-
else value.to_f
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module OID # :nodoc:
-
1
class Hstore < Type::Value # :nodoc:
-
1
include Type::Mutable
-
-
1
def type
-
:hstore
-
end
-
-
1
def type_cast_from_database(value)
-
if value.is_a?(::String)
-
::Hash[value.scan(HstorePair).map { |k, v|
-
v = v.upcase == 'NULL' ? nil : v.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
-
k = k.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
-
[k, v]
-
}]
-
else
-
value
-
end
-
end
-
-
1
def type_cast_for_database(value)
-
if value.is_a?(::Hash)
-
value.map { |k, v| "#{escape_hstore(k)}=>#{escape_hstore(v)}" }.join(', ')
-
else
-
value
-
end
-
end
-
-
1
def accessor
-
ActiveRecord::Store::StringKeyedHashAccessor
-
end
-
-
1
private
-
-
1
HstorePair = begin
-
1
quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/
-
1
unquoted_string = /(?:\\.|[^\s,])[^\s=,\\]*(?:\\.[^\s=,\\]*|=[^,>])*/
-
1
/(#{quoted_string}|#{unquoted_string})\s*=>\s*(#{quoted_string}|#{unquoted_string})/
-
end
-
-
1
def escape_hstore(value)
-
if value.nil?
-
'NULL'
-
else
-
if value == ""
-
'""'
-
else
-
'"%s"' % value.to_s.gsub(/(["\\])/, '\\\\\1')
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module OID # :nodoc:
-
1
class Inet < Cidr # :nodoc:
-
1
def type
-
:inet
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module OID # :nodoc:
-
1
module Infinity # :nodoc:
-
1
def infinity(options = {})
-
options[:negative] ? -::Float::INFINITY : ::Float::INFINITY
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module OID # :nodoc:
-
1
class Integer < Type::Integer # :nodoc:
-
1
include Infinity
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module OID # :nodoc:
-
1
class Json < Type::Value # :nodoc:
-
1
include Type::Mutable
-
-
1
def type
-
:json
-
end
-
-
1
def type_cast_from_database(value)
-
if value.is_a?(::String)
-
::ActiveSupport::JSON.decode(value) rescue nil
-
else
-
super
-
end
-
end
-
-
1
def type_cast_for_database(value)
-
if value.is_a?(::Array) || value.is_a?(::Hash)
-
::ActiveSupport::JSON.encode(value)
-
else
-
super
-
end
-
end
-
-
1
def accessor
-
ActiveRecord::Store::StringKeyedHashAccessor
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module OID # :nodoc:
-
1
class Jsonb < Json # :nodoc:
-
1
def type
-
:jsonb
-
end
-
-
1
def changed_in_place?(raw_old_value, new_value)
-
# Postgres does not preserve insignificant whitespaces when
-
# roundtripping jsonb columns. This causes some false positives for
-
# the comparison here. Therefore, we need to parse and re-dump the
-
# raw value here to ensure the insignificant whitespaces are
-
# consistent with our encoder's output.
-
raw_old_value = type_cast_for_database(type_cast_from_database(raw_old_value))
-
super(raw_old_value, new_value)
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module OID # :nodoc:
-
1
class Money < Type::Decimal # :nodoc:
-
1
include Infinity
-
-
1
class_attribute :precision
-
-
1
def type
-
:money
-
end
-
-
1
def scale
-
2
-
end
-
-
1
def cast_value(value)
-
return value unless ::String === value
-
-
# Because money output is formatted according to the locale, there are two
-
# cases to consider (note the decimal separators):
-
# (1) $12,345,678.12
-
# (2) $12.345.678,12
-
# Negative values are represented as follows:
-
# (3) -$2.55
-
# (4) ($2.55)
-
-
value.sub!(/^\((.+)\)$/, '-\1') # (4)
-
case value
-
when /^-?\D+[\d,]+\.\d{2}$/ # (1)
-
value.gsub!(/[^-\d.]/, '')
-
when /^-?\D+[\d.]+,\d{2}$/ # (2)
-
value.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
-
end
-
-
super(value)
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module OID # :nodoc:
-
1
class Point < Type::Value # :nodoc:
-
1
include Type::Mutable
-
-
1
def type
-
:point
-
end
-
-
1
def type_cast(value)
-
case value
-
when ::String
-
if value[0] == '(' && value[-1] == ')'
-
value = value[1...-1]
-
end
-
type_cast(value.split(','))
-
when ::Array
-
value.map { |v| Float(v) }
-
else
-
value
-
end
-
end
-
-
1
def type_cast_for_database(value)
-
if value.is_a?(::Array)
-
"(#{number_for_point(value[0])},#{number_for_point(value[1])})"
-
else
-
super
-
end
-
end
-
-
1
private
-
-
1
def number_for_point(number)
-
number.to_s.gsub(/\.0$/, '')
-
end
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/string/filters'
-
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module OID # :nodoc:
-
1
class Range < Type::Value # :nodoc:
-
1
attr_reader :subtype, :type
-
-
1
def initialize(subtype, type)
-
@subtype = subtype
-
@type = type
-
end
-
-
1
def type_cast_for_schema(value)
-
value.inspect.gsub('Infinity', '::Float::INFINITY')
-
end
-
-
1
def cast_value(value)
-
return if value == 'empty'
-
return value if value.is_a?(::Range)
-
-
extracted = extract_bounds(value)
-
from = type_cast_single extracted[:from]
-
to = type_cast_single extracted[:to]
-
-
if !infinity?(from) && extracted[:exclude_start]
-
if from.respond_to?(:succ)
-
from = from.succ
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
Excluding the beginning of a Range is only partialy supported
-
through `#succ`. This is not reliable and will be removed in
-
the future.
-
MSG
-
else
-
raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')"
-
end
-
end
-
::Range.new(from, to, extracted[:exclude_end])
-
end
-
-
1
def type_cast_for_database(value)
-
if value.is_a?(::Range)
-
from = type_cast_single_for_database(value.begin)
-
to = type_cast_single_for_database(value.end)
-
"[#{from},#{to}#{value.exclude_end? ? ')' : ']'}"
-
else
-
super
-
end
-
end
-
-
1
private
-
-
1
def type_cast_single(value)
-
infinity?(value) ? value : @subtype.type_cast_from_database(value)
-
end
-
-
1
def type_cast_single_for_database(value)
-
infinity?(value) ? '' : @subtype.type_cast_for_database(value)
-
end
-
-
1
def extract_bounds(value)
-
from, to = value[1..-2].split(',')
-
{
-
from: (value[1] == ',' || from == '-infinity') ? @subtype.infinity(negative: true) : from,
-
to: (value[-2] == ',' || to == 'infinity') ? @subtype.infinity : to,
-
exclude_start: (value[0] == '('),
-
exclude_end: (value[-1] == ')')
-
}
-
end
-
-
1
def infinity?(value)
-
value.respond_to?(:infinite?) && value.infinite?
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module OID # :nodoc:
-
1
class SpecializedString < Type::String # :nodoc:
-
1
attr_reader :type
-
-
1
def initialize(type)
-
@type = type
-
end
-
-
1
def text?
-
false
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module OID # :nodoc:
-
1
class Time < Type::Time # :nodoc:
-
1
include Infinity
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module OID # :nodoc:
-
# This class uses the data from PostgreSQL pg_type table to build
-
# the OID -> Type mapping.
-
# - OID is an integer representing the type.
-
# - Type is an OID::Type object.
-
# This class has side effects on the +store+ passed during initialization.
-
1
class TypeMapInitializer # :nodoc:
-
1
def initialize(store)
-
@store = store
-
end
-
-
1
def run(records)
-
nodes = records.reject { |row| @store.key? row['oid'].to_i }
-
mapped, nodes = nodes.partition { |row| @store.key? row['typname'] }
-
ranges, nodes = nodes.partition { |row| row['typtype'] == 'r'.freeze }
-
enums, nodes = nodes.partition { |row| row['typtype'] == 'e'.freeze }
-
domains, nodes = nodes.partition { |row| row['typtype'] == 'd'.freeze }
-
arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in'.freeze }
-
composites, nodes = nodes.partition { |row| row['typelem'].to_i != 0 }
-
-
mapped.each { |row| register_mapped_type(row) }
-
enums.each { |row| register_enum_type(row) }
-
domains.each { |row| register_domain_type(row) }
-
arrays.each { |row| register_array_type(row) }
-
ranges.each { |row| register_range_type(row) }
-
composites.each { |row| register_composite_type(row) }
-
end
-
-
1
def query_conditions_for_initial_load(type_map)
-
known_type_names = type_map.keys.map { |n| "'#{n}'" }
-
known_type_types = %w('r' 'e' 'd')
-
<<-SQL % [known_type_names.join(", "), known_type_types.join(", ")]
-
WHERE
-
t.typname IN (%s)
-
OR t.typtype IN (%s)
-
OR t.typinput = 'array_in(cstring,oid,integer)'::regprocedure
-
OR t.typelem != 0
-
SQL
-
end
-
-
1
private
-
1
def register_mapped_type(row)
-
alias_type row['oid'], row['typname']
-
end
-
-
1
def register_enum_type(row)
-
register row['oid'], OID::Enum.new
-
end
-
-
1
def register_array_type(row)
-
register_with_subtype(row['oid'], row['typelem'].to_i) do |subtype|
-
OID::Array.new(subtype, row['typdelim'])
-
end
-
end
-
-
1
def register_range_type(row)
-
register_with_subtype(row['oid'], row['rngsubtype'].to_i) do |subtype|
-
OID::Range.new(subtype, row['typname'].to_sym)
-
end
-
end
-
-
1
def register_domain_type(row)
-
if base_type = @store.lookup(row["typbasetype"].to_i)
-
register row['oid'], base_type
-
else
-
warn "unknown base type (OID: #{row["typbasetype"]}) for domain #{row["typname"]}."
-
end
-
end
-
-
1
def register_composite_type(row)
-
if subtype = @store.lookup(row['typelem'].to_i)
-
register row['oid'], OID::Vector.new(row['typdelim'], subtype)
-
end
-
end
-
-
1
def register(oid, oid_type = nil, &block)
-
oid = assert_valid_registration(oid, oid_type || block)
-
if block_given?
-
@store.register_type(oid, &block)
-
else
-
@store.register_type(oid, oid_type)
-
end
-
end
-
-
1
def alias_type(oid, target)
-
oid = assert_valid_registration(oid, target)
-
@store.alias_type(oid, target)
-
end
-
-
1
def register_with_subtype(oid, target_oid)
-
if @store.key?(target_oid)
-
register(oid) do |_, *args|
-
yield @store.lookup(target_oid, *args)
-
end
-
end
-
end
-
-
1
def assert_valid_registration(oid, oid_type)
-
raise ArgumentError, "can't register nil type for OID #{oid}" if oid_type.nil?
-
oid.to_i
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module OID # :nodoc:
-
1
class Uuid < Type::Value # :nodoc:
-
1
ACCEPTABLE_UUID = %r{\A\{?([a-fA-F0-9]{4}-?){8}\}?\z}x
-
-
1
alias_method :type_cast_for_database, :type_cast_from_database
-
-
1
def type
-
:uuid
-
end
-
-
1
def type_cast(value)
-
value.to_s[ACCEPTABLE_UUID, 0]
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module OID # :nodoc:
-
1
class Vector < Type::Value # :nodoc:
-
1
attr_reader :delim, :subtype
-
-
# +delim+ corresponds to the `typdelim` column in the pg_types
-
# table. +subtype+ is derived from the `typelem` column in the
-
# pg_types table.
-
1
def initialize(delim, subtype)
-
@delim = delim
-
@subtype = subtype
-
end
-
-
# FIXME: this should probably split on +delim+ and use +subtype+
-
# to cast the values. Unfortunately, the current Rails behavior
-
# is to just return the string.
-
1
def type_cast(value)
-
value
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module OID # :nodoc:
-
1
class Xml < Type::String # :nodoc:
-
1
def type
-
:xml
-
end
-
-
1
def type_cast_for_database(value)
-
return unless value
-
Data.new(super)
-
end
-
-
1
class Data # :nodoc:
-
1
def initialize(value)
-
@value = value
-
end
-
-
1
def to_s
-
@value
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module Quoting
-
# Escapes binary strings for bytea input to the database.
-
1
def escape_bytea(value)
-
@connection.escape_bytea(value) if value
-
end
-
-
# Unescapes bytea output from a database to the binary string it represents.
-
# NOTE: This is NOT an inverse of escape_bytea! This is only to be used
-
# on escaped binary output from database drive.
-
1
def unescape_bytea(value)
-
@connection.unescape_bytea(value) if value
-
end
-
-
# Quotes strings for use in SQL input.
-
1
def quote_string(s) #:nodoc:
-
@connection.escape(s)
-
end
-
-
# Checks the following cases:
-
#
-
# - table_name
-
# - "table.name"
-
# - schema_name.table_name
-
# - schema_name."table.name"
-
# - "schema.name".table_name
-
# - "schema.name"."table.name"
-
1
def quote_table_name(name)
-
Utils.extract_schema_qualified_name(name.to_s).quoted
-
end
-
-
1
def quote_table_name_for_assignment(table, attr)
-
quote_column_name(attr)
-
end
-
-
# Quotes column names for use in SQL queries.
-
1
def quote_column_name(name) #:nodoc:
-
PGconn.quote_ident(name.to_s)
-
end
-
-
# Quote date/time values for use in SQL input. Includes microseconds
-
# if the value is a Time responding to usec.
-
1
def quoted_date(value) #:nodoc:
-
result = super
-
if value.acts_like?(:time) && value.respond_to?(:usec)
-
result = "#{result}.#{sprintf("%06d", value.usec)}"
-
end
-
-
if value.year <= 0
-
bce_year = format("%04d", -value.year + 1)
-
result = result.sub(/^-?\d+/, bce_year) + " BC"
-
end
-
result
-
end
-
-
# Does not quote function default values for UUID columns
-
1
def quote_default_value(value, column) #:nodoc:
-
if column.type == :uuid && value =~ /\(\)/
-
value
-
else
-
quote(value, column)
-
end
-
end
-
-
1
private
-
-
1
def _quote(value)
-
case value
-
when Type::Binary::Data
-
"'#{escape_bytea(value.to_s)}'"
-
when OID::Xml::Data
-
"xml '#{quote_string(value.to_s)}'"
-
when OID::Bit::Data
-
if value.binary?
-
"B'#{value}'"
-
elsif value.hex?
-
"X'#{value}'"
-
end
-
when Float
-
if value.infinite? || value.nan?
-
"'#{value}'"
-
else
-
super
-
end
-
else
-
super
-
end
-
end
-
-
1
def _type_cast(value)
-
case value
-
when Type::Binary::Data
-
# Return a bind param hash with format as binary.
-
# See http://deveiate.org/code/pg/PGconn.html#method-i-exec_prepared-doc
-
# for more information
-
{ value: value.to_s, format: 1 }
-
when OID::Xml::Data, OID::Bit::Data
-
value.to_s
-
else
-
super
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module ReferentialIntegrity # :nodoc:
-
1
def supports_disable_referential_integrity? # :nodoc:
-
true
-
end
-
-
1
def disable_referential_integrity # :nodoc:
-
if supports_disable_referential_integrity?
-
begin
-
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
-
rescue
-
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER USER" }.join(";"))
-
end
-
end
-
yield
-
ensure
-
if supports_disable_referential_integrity?
-
begin
-
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
-
rescue
-
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER USER" }.join(";"))
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
module ColumnMethods
-
1
def xml(*args)
-
options = args.extract_options!
-
column(args[0], :xml, options)
-
end
-
-
1
def tsvector(*args)
-
options = args.extract_options!
-
column(args[0], :tsvector, options)
-
end
-
-
1
def int4range(name, options = {})
-
column(name, :int4range, options)
-
end
-
-
1
def int8range(name, options = {})
-
column(name, :int8range, options)
-
end
-
-
1
def tsrange(name, options = {})
-
column(name, :tsrange, options)
-
end
-
-
1
def tstzrange(name, options = {})
-
column(name, :tstzrange, options)
-
end
-
-
1
def numrange(name, options = {})
-
column(name, :numrange, options)
-
end
-
-
1
def daterange(name, options = {})
-
column(name, :daterange, options)
-
end
-
-
1
def hstore(name, options = {})
-
column(name, :hstore, options)
-
end
-
-
1
def ltree(name, options = {})
-
column(name, :ltree, options)
-
end
-
-
1
def inet(name, options = {})
-
column(name, :inet, options)
-
end
-
-
1
def cidr(name, options = {})
-
column(name, :cidr, options)
-
end
-
-
1
def macaddr(name, options = {})
-
column(name, :macaddr, options)
-
end
-
-
1
def uuid(name, options = {})
-
column(name, :uuid, options)
-
end
-
-
1
def json(name, options = {})
-
column(name, :json, options)
-
end
-
-
1
def jsonb(name, options = {})
-
column(name, :jsonb, options)
-
end
-
-
1
def citext(name, options = {})
-
column(name, :citext, options)
-
end
-
-
1
def point(name, options = {})
-
column(name, :point, options)
-
end
-
-
1
def bit(name, options = {})
-
column(name, :bit, options)
-
end
-
-
1
def bit_varying(name, options = {})
-
column(name, :bit_varying, options)
-
end
-
-
1
def money(name, options = {})
-
column(name, :money, options)
-
end
-
end
-
-
1
class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
-
1
attr_accessor :array
-
end
-
-
1
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
-
1
include ColumnMethods
-
-
# Defines the primary key field.
-
# Use of the native PostgreSQL UUID type is supported, and can be used
-
# by defining your tables as such:
-
#
-
# create_table :stuffs, id: :uuid do |t|
-
# t.string :content
-
# t.timestamps
-
# end
-
#
-
# By default, this will use the +uuid_generate_v4()+ function from the
-
# +uuid-ossp+ extension, which MUST be enabled on your database. To enable
-
# the +uuid-ossp+ extension, you can use the +enable_extension+ method in your
-
# migrations. To use a UUID primary key without +uuid-ossp+ enabled, you can
-
# set the +:default+ option to +nil+:
-
#
-
# create_table :stuffs, id: false do |t|
-
# t.primary_key :id, :uuid, default: nil
-
# t.uuid :foo_id
-
# t.timestamps
-
# end
-
#
-
# You may also pass a different UUID generation function from +uuid-ossp+
-
# or another library.
-
#
-
# Note that setting the UUID primary key default value to +nil+ will
-
# require you to assure that you always provide a UUID value before saving
-
# a record (as primary keys cannot be +nil+). This might be done via the
-
# +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
-
1
def primary_key(name, type = :primary_key, options = {})
-
return super unless type == :uuid
-
options[:default] = options.fetch(:default, 'uuid_generate_v4()')
-
options[:primary_key] = true
-
column name, type, options
-
end
-
-
1
def new_column_definition(name, type, options) # :nodoc:
-
column = super
-
column.array = options[:array]
-
column
-
end
-
-
1
private
-
-
1
def create_column_definition(name, type)
-
PostgreSQL::ColumnDefinition.new name, type
-
end
-
end
-
-
1
class Table < ActiveRecord::ConnectionAdapters::Table
-
1
include ColumnMethods
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
1
class SchemaCreation < AbstractAdapter::SchemaCreation
-
1
private
-
-
1
def visit_ColumnDefinition(o)
-
sql = super
-
if o.primary_key? && o.type != :primary_key
-
sql << " PRIMARY KEY "
-
add_column_options!(sql, column_options(o))
-
end
-
sql
-
end
-
-
1
def add_column_options!(sql, options)
-
if options[:array] || options[:column].try(:array)
-
sql << '[]'
-
end
-
-
column = options.fetch(:column) { return super }
-
if column.type == :uuid && options[:default] =~ /\(\)/
-
sql << " DEFAULT #{options[:default]}"
-
else
-
super
-
end
-
end
-
-
1
def type_for_column(column)
-
if column.array
-
@conn.lookup_cast_type("#{column.sql_type}[]")
-
else
-
super
-
end
-
end
-
end
-
-
1
module SchemaStatements
-
# Drops the database specified on the +name+ attribute
-
# and creates it again using the provided +options+.
-
1
def recreate_database(name, options = {}) #:nodoc:
-
drop_database(name)
-
create_database(name, options)
-
end
-
-
# Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
-
# <tt>:encoding</tt> (defaults to utf8), <tt>:collation</tt>, <tt>:ctype</tt>,
-
# <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
-
# <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
-
#
-
# Example:
-
# create_database config[:database], config
-
# create_database 'foo_development', encoding: 'unicode'
-
1
def create_database(name, options = {})
-
options = { encoding: 'utf8' }.merge!(options.symbolize_keys)
-
-
option_string = options.inject("") do |memo, (key, value)|
-
memo += case key
-
when :owner
-
" OWNER = \"#{value}\""
-
when :template
-
" TEMPLATE = \"#{value}\""
-
when :encoding
-
" ENCODING = '#{value}'"
-
when :collation
-
" LC_COLLATE = '#{value}'"
-
when :ctype
-
" LC_CTYPE = '#{value}'"
-
when :tablespace
-
" TABLESPACE = \"#{value}\""
-
when :connection_limit
-
" CONNECTION LIMIT = #{value}"
-
else
-
""
-
end
-
end
-
-
execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
-
end
-
-
# Drops a PostgreSQL database.
-
#
-
# Example:
-
# drop_database 'matt_development'
-
1
def drop_database(name) #:nodoc:
-
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
-
end
-
-
# Returns the list of all tables in the schema search path.
-
1
def tables(name = nil)
-
query(<<-SQL, 'SCHEMA').map { |row| row[0] }
-
SELECT tablename
-
FROM pg_tables
-
WHERE schemaname = ANY (current_schemas(false))
-
SQL
-
end
-
-
# Returns true if table exists.
-
# If the schema is not specified as part of +name+ then it will only find tables within
-
# the current schema search path (regardless of permissions to access tables in other schemas)
-
1
def table_exists?(name)
-
name = Utils.extract_schema_qualified_name(name.to_s)
-
return false unless name.identifier
-
-
exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
-
SELECT COUNT(*)
-
FROM pg_class c
-
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
-
WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
-
AND c.relname = '#{name.identifier}'
-
AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
-
SQL
-
end
-
-
1
def drop_table(table_name, options = {})
-
execute "DROP TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
-
end
-
-
# Returns true if schema exists.
-
1
def schema_exists?(name)
-
exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
-
SELECT COUNT(*)
-
FROM pg_namespace
-
WHERE nspname = '#{name}'
-
SQL
-
end
-
-
1
def index_name_exists?(table_name, index_name, default)
-
exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
-
SELECT COUNT(*)
-
FROM pg_class t
-
INNER JOIN pg_index d ON t.oid = d.indrelid
-
INNER JOIN pg_class i ON d.indexrelid = i.oid
-
WHERE i.relkind = 'i'
-
AND i.relname = '#{index_name}'
-
AND t.relname = '#{table_name}'
-
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
-
SQL
-
end
-
-
# Returns an array of indexes for the given table.
-
1
def indexes(table_name, name = nil)
-
result = query(<<-SQL, 'SCHEMA')
-
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
-
FROM pg_class t
-
INNER JOIN pg_index d ON t.oid = d.indrelid
-
INNER JOIN pg_class i ON d.indexrelid = i.oid
-
WHERE i.relkind = 'i'
-
AND d.indisprimary = 'f'
-
AND t.relname = '#{table_name}'
-
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
-
ORDER BY i.relname
-
SQL
-
-
result.map do |row|
-
index_name = row[0]
-
unique = row[1] == 't'
-
indkey = row[2].split(" ")
-
inddef = row[3]
-
oid = row[4]
-
-
columns = Hash[query(<<-SQL, "SCHEMA")]
-
SELECT a.attnum, a.attname
-
FROM pg_attribute a
-
WHERE a.attrelid = #{oid}
-
AND a.attnum IN (#{indkey.join(",")})
-
SQL
-
-
column_names = columns.values_at(*indkey).compact
-
-
unless column_names.empty?
-
# add info on sort order for columns (only desc order is explicitly specified, asc is the default)
-
desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
-
orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
-
where = inddef.scan(/WHERE (.+)$/).flatten[0]
-
using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
-
-
IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using)
-
end
-
end.compact
-
end
-
-
# Returns the list of all column definitions for a table.
-
1
def columns(table_name)
-
# Limit, precision, and scale are all handled by the superclass.
-
column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod|
-
oid = get_oid_type(oid.to_i, fmod.to_i, column_name, type)
-
default_value = extract_value_from_default(oid, default)
-
default_function = extract_default_function(default_value, default)
-
new_column(column_name, default_value, oid, type, notnull == 'f', default_function)
-
end
-
end
-
-
1
def new_column(name, default, cast_type, sql_type = nil, null = true, default_function = nil) # :nodoc:
-
PostgreSQLColumn.new(name, default, cast_type, sql_type, null, default_function)
-
end
-
-
# Returns the current database name.
-
1
def current_database
-
query('select current_database()', 'SCHEMA')[0][0]
-
end
-
-
# Returns the current schema name.
-
1
def current_schema
-
query('SELECT current_schema', 'SCHEMA')[0][0]
-
end
-
-
# Returns the current database encoding format.
-
1
def encoding
-
query(<<-end_sql, 'SCHEMA')[0][0]
-
SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
-
WHERE pg_database.datname LIKE '#{current_database}'
-
end_sql
-
end
-
-
# Returns the current database collation.
-
1
def collation
-
query(<<-end_sql, 'SCHEMA')[0][0]
-
SELECT pg_database.datcollate FROM pg_database WHERE pg_database.datname LIKE '#{current_database}'
-
end_sql
-
end
-
-
# Returns the current database ctype.
-
1
def ctype
-
query(<<-end_sql, 'SCHEMA')[0][0]
-
SELECT pg_database.datctype FROM pg_database WHERE pg_database.datname LIKE '#{current_database}'
-
end_sql
-
end
-
-
# Returns an array of schema names.
-
1
def schema_names
-
query(<<-SQL, 'SCHEMA').flatten
-
SELECT nspname
-
FROM pg_namespace
-
WHERE nspname !~ '^pg_.*'
-
AND nspname NOT IN ('information_schema')
-
ORDER by nspname;
-
SQL
-
end
-
-
# Creates a schema for the given schema name.
-
1
def create_schema schema_name
-
execute "CREATE SCHEMA #{schema_name}"
-
end
-
-
# Drops the schema for the given schema name.
-
1
def drop_schema schema_name
-
execute "DROP SCHEMA #{schema_name} CASCADE"
-
end
-
-
# Sets the schema search path to a string of comma-separated schema names.
-
# Names beginning with $ have to be quoted (e.g. $user => '$user').
-
# See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
-
#
-
# This should be not be called manually but set in database.yml.
-
1
def schema_search_path=(schema_csv)
-
if schema_csv
-
execute("SET search_path TO #{schema_csv}", 'SCHEMA')
-
@schema_search_path = schema_csv
-
end
-
end
-
-
# Returns the active schema search path.
-
1
def schema_search_path
-
@schema_search_path ||= query('SHOW search_path', 'SCHEMA')[0][0]
-
end
-
-
# Returns the current client message level.
-
1
def client_min_messages
-
query('SHOW client_min_messages', 'SCHEMA')[0][0]
-
end
-
-
# Set the client message level.
-
1
def client_min_messages=(level)
-
execute("SET client_min_messages TO '#{level}'", 'SCHEMA')
-
end
-
-
# Returns the sequence name for a table's primary key or some other specified key.
-
1
def default_sequence_name(table_name, pk = nil) #:nodoc:
-
result = serial_sequence(table_name, pk || 'id')
-
return nil unless result
-
Utils.extract_schema_qualified_name(result).to_s
-
rescue ActiveRecord::StatementInvalid
-
PostgreSQL::Name.new(nil, "#{table_name}_#{pk || 'id'}_seq").to_s
-
end
-
-
1
def serial_sequence(table, column)
-
result = exec_query(<<-eosql, 'SCHEMA')
-
SELECT pg_get_serial_sequence('#{table}', '#{column}')
-
eosql
-
result.rows.first.first
-
end
-
-
# Sets the sequence of a table's primary key to the specified value.
-
1
def set_pk_sequence!(table, value) #:nodoc:
-
pk, sequence = pk_and_sequence_for(table)
-
-
if pk
-
if sequence
-
quoted_sequence = quote_table_name(sequence)
-
-
select_value <<-end_sql, 'SCHEMA'
-
SELECT setval('#{quoted_sequence}', #{value})
-
end_sql
-
else
-
@logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
-
end
-
end
-
end
-
-
# Resets the sequence of a table's primary key to the maximum value.
-
1
def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
-
unless pk and sequence
-
default_pk, default_sequence = pk_and_sequence_for(table)
-
-
pk ||= default_pk
-
sequence ||= default_sequence
-
end
-
-
if @logger && pk && !sequence
-
@logger.warn "#{table} has primary key #{pk} with no default sequence"
-
end
-
-
if pk && sequence
-
quoted_sequence = quote_table_name(sequence)
-
-
select_value <<-end_sql, 'SCHEMA'
-
SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
-
end_sql
-
end
-
end
-
-
# Returns a table's primary key and belonging sequence.
-
1
def pk_and_sequence_for(table) #:nodoc:
-
# First try looking for a sequence with a dependency on the
-
# given table's primary key.
-
result = query(<<-end_sql, 'SCHEMA')[0]
-
SELECT attr.attname, nsp.nspname, seq.relname
-
FROM pg_class seq,
-
pg_attribute attr,
-
pg_depend dep,
-
pg_constraint cons,
-
pg_namespace nsp
-
WHERE seq.oid = dep.objid
-
AND seq.relkind = 'S'
-
AND attr.attrelid = dep.refobjid
-
AND attr.attnum = dep.refobjsubid
-
AND attr.attrelid = cons.conrelid
-
AND attr.attnum = cons.conkey[1]
-
AND seq.relnamespace = nsp.oid
-
AND cons.contype = 'p'
-
AND dep.classid = 'pg_class'::regclass
-
AND dep.refobjid = '#{quote_table_name(table)}'::regclass
-
end_sql
-
-
if result.nil? or result.empty?
-
result = query(<<-end_sql, 'SCHEMA')[0]
-
SELECT attr.attname, nsp.nspname,
-
CASE
-
WHEN pg_get_expr(def.adbin, def.adrelid) !~* 'nextval' THEN NULL
-
WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
-
substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2),
-
strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1)
-
ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2)
-
END
-
FROM pg_class t
-
JOIN pg_attribute attr ON (t.oid = attrelid)
-
JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
-
JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
-
JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid)
-
WHERE t.oid = '#{quote_table_name(table)}'::regclass
-
AND cons.contype = 'p'
-
AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
-
end_sql
-
end
-
-
pk = result.shift
-
if result.last
-
[pk, PostgreSQL::Name.new(*result)]
-
else
-
[pk, nil]
-
end
-
rescue
-
nil
-
end
-
-
# Returns just a table's primary key
-
1
def primary_key(table)
-
pks = exec_query(<<-end_sql, 'SCHEMA').rows
-
SELECT attr.attname
-
FROM pg_attribute attr
-
INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey)
-
WHERE cons.contype = 'p'
-
AND cons.conrelid = '#{quote_table_name(table)}'::regclass
-
end_sql
-
return nil unless pks.count == 1
-
pks[0][0]
-
end
-
-
# Renames a table.
-
# Also renames a table's primary key sequence if the sequence name exists and
-
# matches the Active Record default.
-
#
-
# Example:
-
# rename_table('octopuses', 'octopi')
-
1
def rename_table(table_name, new_name)
-
clear_cache!
-
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
-
pk, seq = pk_and_sequence_for(new_name)
-
if seq && seq.identifier == "#{table_name}_#{pk}_seq"
-
new_seq = "#{new_name}_#{pk}_seq"
-
idx = "#{table_name}_pkey"
-
new_idx = "#{new_name}_pkey"
-
execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
-
execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}"
-
end
-
-
rename_table_indexes(table_name, new_name)
-
end
-
-
1
def add_column(table_name, column_name, type, options = {}) #:nodoc:
-
clear_cache!
-
super
-
end
-
-
# Changes the column of a table.
-
1
def change_column(table_name, column_name, type, options = {})
-
clear_cache!
-
quoted_table_name = quote_table_name(table_name)
-
sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale])
-
sql_type << "[]" if options[:array]
-
sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{sql_type}"
-
sql << " USING #{options[:using]}" if options[:using]
-
if options[:cast_as]
-
sql << " USING CAST(#{quote_column_name(column_name)} AS #{type_to_sql(options[:cast_as], options[:limit], options[:precision], options[:scale])})"
-
end
-
execute sql
-
-
change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
-
change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
-
end
-
-
# Changes the default value of a table column.
-
1
def change_column_default(table_name, column_name, default)
-
clear_cache!
-
column = column_for(table_name, column_name)
-
return unless column
-
-
alter_column_query = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} %s"
-
if default.nil?
-
# <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
-
# cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
-
execute alter_column_query % "DROP DEFAULT"
-
else
-
execute alter_column_query % "SET DEFAULT #{quote_default_value(default, column)}"
-
end
-
end
-
-
1
def change_column_null(table_name, column_name, null, default = nil)
-
clear_cache!
-
unless null || default.nil?
-
column = column_for(table_name, column_name)
-
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_value(default, column)} WHERE #{quote_column_name(column_name)} IS NULL") if column
-
end
-
execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
-
end
-
-
# Renames a column in a table.
-
1
def rename_column(table_name, column_name, new_column_name) #:nodoc:
-
clear_cache!
-
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
-
rename_column_indexes(table_name, column_name, new_column_name)
-
end
-
-
1
def add_index(table_name, column_name, options = {}) #:nodoc:
-
index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
-
execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}"
-
end
-
-
1
def remove_index!(table_name, index_name) #:nodoc:
-
execute "DROP INDEX #{quote_table_name(index_name)}"
-
end
-
-
1
def rename_index(table_name, old_name, new_name)
-
validate_index_length!(table_name, new_name)
-
-
execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
-
end
-
-
1
def foreign_keys(table_name)
-
fk_info = select_all <<-SQL.strip_heredoc
-
SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete
-
FROM pg_constraint c
-
JOIN pg_class t1 ON c.conrelid = t1.oid
-
JOIN pg_class t2 ON c.confrelid = t2.oid
-
JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
-
JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
-
JOIN pg_namespace t3 ON c.connamespace = t3.oid
-
WHERE c.contype = 'f'
-
AND t1.relname = #{quote(table_name)}
-
AND t3.nspname = ANY (current_schemas(false))
-
ORDER BY c.conname
-
SQL
-
-
fk_info.map do |row|
-
options = {
-
column: row['column'],
-
name: row['name'],
-
primary_key: row['primary_key']
-
}
-
-
options[:on_delete] = extract_foreign_key_action(row['on_delete'])
-
options[:on_update] = extract_foreign_key_action(row['on_update'])
-
-
ForeignKeyDefinition.new(table_name, row['to_table'], options)
-
end
-
end
-
-
1
def extract_foreign_key_action(specifier) # :nodoc:
-
case specifier
-
when 'c'; :cascade
-
when 'n'; :nullify
-
when 'r'; :restrict
-
end
-
end
-
-
1
def index_name_length
-
63
-
end
-
-
# Maps logical Rails types to PostgreSQL-specific data types.
-
1
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
-
case type.to_s
-
when 'binary'
-
# PostgreSQL doesn't support limits on binary (bytea) columns.
-
# The hard limit is 1Gb, because of a 32-bit size field, and TOAST.
-
case limit
-
when nil, 0..0x3fffffff; super(type)
-
else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
-
end
-
when 'text'
-
# PostgreSQL doesn't support limits on text columns.
-
# The hard limit is 1Gb, according to section 8.3 in the manual.
-
case limit
-
when nil, 0..0x3fffffff; super(type)
-
else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
-
end
-
when 'integer'
-
return 'integer' unless limit
-
-
case limit
-
when 1, 2; 'smallint'
-
when 3, 4; 'integer'
-
when 5..8; 'bigint'
-
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
-
end
-
when 'datetime'
-
return super unless precision
-
-
case precision
-
when 0..6; "timestamp(#{precision})"
-
else raise(ActiveRecordError, "No timestamp type has precision of #{precision}. The allowed range of precision is from 0 to 6")
-
end
-
else
-
super
-
end
-
end
-
-
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
-
# requires that the ORDER BY include the distinct column.
-
1
def columns_for_distinct(columns, orders) #:nodoc:
-
order_columns = orders.reject(&:blank?).map{ |s|
-
# Convert Arel node to string
-
s = s.to_sql unless s.is_a?(String)
-
# Remove any ASC/DESC modifiers
-
s.gsub(/\s+(?:ASC|DESC)\b/i, '')
-
.gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, '')
-
}.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
-
-
[super, *order_columns].join(', ')
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
module PostgreSQL
-
# Value Object to hold a schema qualified name.
-
# This is usually the name of a PostgreSQL relation but it can also represent
-
# schema qualified type names. +schema+ and +identifier+ are unquoted to prevent
-
# double quoting.
-
1
class Name # :nodoc:
-
1
SEPARATOR = "."
-
1
attr_reader :schema, :identifier
-
-
1
def initialize(schema, identifier)
-
@schema, @identifier = unquote(schema), unquote(identifier)
-
end
-
-
1
def to_s
-
parts.join SEPARATOR
-
end
-
-
1
def quoted
-
if schema
-
PGconn.quote_ident(schema) << SEPARATOR << PGconn.quote_ident(identifier)
-
else
-
PGconn.quote_ident(identifier)
-
end
-
end
-
-
1
def ==(o)
-
o.class == self.class && o.parts == parts
-
end
-
1
alias_method :eql?, :==
-
-
1
def hash
-
parts.hash
-
end
-
-
1
protected
-
1
def unquote(part)
-
if part && part.start_with?('"')
-
part[1..-2]
-
else
-
part
-
end
-
end
-
-
1
def parts
-
@parts ||= [@schema, @identifier].compact
-
end
-
end
-
-
1
module Utils # :nodoc:
-
1
extend self
-
-
# Returns an instance of <tt>ActiveRecord::ConnectionAdapters::PostgreSQL::Name</tt>
-
# extracted from +string+.
-
# +schema+ is nil if not specified in +string+.
-
# +schema+ and +identifier+ exclude surrounding quotes (regardless of whether provided in +string+)
-
# +string+ supports the range of schema/table references understood by PostgreSQL, for example:
-
#
-
# * <tt>table_name</tt>
-
# * <tt>"table.name"</tt>
-
# * <tt>schema_name.table_name</tt>
-
# * <tt>schema_name."table.name"</tt>
-
# * <tt>"schema_name".table_name</tt>
-
# * <tt>"schema.name"."table name"</tt>
-
1
def extract_schema_qualified_name(string)
-
schema, table = string.scan(/[^".\s]+|"[^"]*"/)
-
if table.nil?
-
table = schema
-
schema = nil
-
end
-
PostgreSQL::Name.new(schema, table)
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_record/connection_adapters/abstract_adapter'
-
1
require 'active_record/connection_adapters/statement_pool'
-
-
1
require 'active_record/connection_adapters/postgresql/utils'
-
1
require 'active_record/connection_adapters/postgresql/column'
-
1
require 'active_record/connection_adapters/postgresql/oid'
-
1
require 'active_record/connection_adapters/postgresql/quoting'
-
1
require 'active_record/connection_adapters/postgresql/referential_integrity'
-
1
require 'active_record/connection_adapters/postgresql/schema_definitions'
-
1
require 'active_record/connection_adapters/postgresql/schema_statements'
-
1
require 'active_record/connection_adapters/postgresql/database_statements'
-
-
1
require 'arel/visitors/bind_visitor'
-
-
# Make sure we're using pg high enough for PGResult#values
-
1
gem 'pg', '~> 0.15'
-
1
require 'pg'
-
-
1
require 'ipaddr'
-
-
1
module ActiveRecord
-
1
module ConnectionHandling # :nodoc:
-
1
VALID_CONN_PARAMS = [:host, :hostaddr, :port, :dbname, :user, :password, :connect_timeout,
-
:client_encoding, :options, :application_name, :fallback_application_name,
-
:keepalives, :keepalives_idle, :keepalives_interval, :keepalives_count,
-
:tty, :sslmode, :requiressl, :sslcompression, :sslcert, :sslkey,
-
:sslrootcert, :sslcrl, :requirepeer, :krbsrvname, :gsslib, :service]
-
-
# Establishes a connection to the database that's used by all Active Record objects
-
1
def postgresql_connection(config)
-
conn_params = config.symbolize_keys
-
-
conn_params.delete_if { |_, v| v.nil? }
-
-
# Map ActiveRecords param names to PGs.
-
conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
-
conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
-
-
# Forward only valid config params to PGconn.connect.
-
conn_params.keep_if { |k, _| VALID_CONN_PARAMS.include?(k) }
-
-
# The postgres drivers don't allow the creation of an unconnected PGconn object,
-
# so just pass a nil connection object for the time being.
-
ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, conn_params, config)
-
end
-
end
-
-
1
module ConnectionAdapters
-
# The PostgreSQL adapter works with the native C (https://bitbucket.org/ged/ruby-pg) driver.
-
#
-
# Options:
-
#
-
# * <tt>:host</tt> - Defaults to a Unix-domain socket in /tmp. On machines without Unix-domain sockets,
-
# the default is to connect to localhost.
-
# * <tt>:port</tt> - Defaults to 5432.
-
# * <tt>:username</tt> - Defaults to be the same as the operating system name of the user running the application.
-
# * <tt>:password</tt> - Password to be used if the server demands password authentication.
-
# * <tt>:database</tt> - Defaults to be the same as the user name.
-
# * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
-
# as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
-
# * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
-
# <encoding></tt> call on the connection.
-
# * <tt>:min_messages</tt> - An optional client min messages that is used in a
-
# <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
-
# * <tt>:variables</tt> - An optional hash of additional parameters that
-
# will be used in <tt>SET SESSION key = val</tt> calls on the connection.
-
# * <tt>:insert_returning</tt> - An optional boolean to control the use of <tt>RETURNING</tt> for <tt>INSERT</tt> statements
-
# defaults to true.
-
#
-
# Any further options are used as connection parameters to libpq. See
-
# http://www.postgresql.org/docs/9.1/static/libpq-connect.html for the
-
# list of parameters.
-
#
-
# In addition, default connection parameters of libpq can be set per environment variables.
-
# See http://www.postgresql.org/docs/9.1/static/libpq-envars.html .
-
1
class PostgreSQLAdapter < AbstractAdapter
-
1
ADAPTER_NAME = 'PostgreSQL'.freeze
-
-
1
NATIVE_DATABASE_TYPES = {
-
primary_key: "serial primary key",
-
bigserial: "bigserial",
-
string: { name: "character varying" },
-
text: { name: "text" },
-
integer: { name: "integer" },
-
float: { name: "float" },
-
decimal: { name: "decimal" },
-
datetime: { name: "timestamp" },
-
time: { name: "time" },
-
date: { name: "date" },
-
daterange: { name: "daterange" },
-
numrange: { name: "numrange" },
-
tsrange: { name: "tsrange" },
-
tstzrange: { name: "tstzrange" },
-
int4range: { name: "int4range" },
-
int8range: { name: "int8range" },
-
binary: { name: "bytea" },
-
boolean: { name: "boolean" },
-
bigint: { name: "bigint" },
-
xml: { name: "xml" },
-
tsvector: { name: "tsvector" },
-
hstore: { name: "hstore" },
-
inet: { name: "inet" },
-
cidr: { name: "cidr" },
-
macaddr: { name: "macaddr" },
-
uuid: { name: "uuid" },
-
json: { name: "json" },
-
jsonb: { name: "jsonb" },
-
ltree: { name: "ltree" },
-
citext: { name: "citext" },
-
point: { name: "point" },
-
bit: { name: "bit" },
-
bit_varying: { name: "bit varying" },
-
money: { name: "money" },
-
}
-
-
1
OID = PostgreSQL::OID #:nodoc:
-
-
1
include PostgreSQL::Quoting
-
1
include PostgreSQL::ReferentialIntegrity
-
1
include PostgreSQL::SchemaStatements
-
1
include PostgreSQL::DatabaseStatements
-
1
include Savepoints
-
-
1
def schema_creation # :nodoc:
-
PostgreSQL::SchemaCreation.new self
-
end
-
-
# Adds +:array+ option to the default set provided by the
-
# AbstractAdapter
-
1
def prepare_column_options(column, types) # :nodoc:
-
spec = super
-
spec[:array] = 'true' if column.respond_to?(:array) && column.array
-
spec[:default] = "\"#{column.default_function}\"" if column.default_function
-
spec
-
end
-
-
# Adds +:array+ as a valid migration key
-
1
def migration_keys
-
super + [:array]
-
end
-
-
# Returns +true+, since this connection adapter supports prepared statement
-
# caching.
-
1
def supports_statement_cache?
-
true
-
end
-
-
1
def supports_index_sort_order?
-
true
-
end
-
-
1
def supports_partial_index?
-
true
-
end
-
-
1
def supports_transaction_isolation?
-
true
-
end
-
-
1
def supports_foreign_keys?
-
true
-
end
-
-
1
def supports_views?
-
true
-
end
-
-
1
def index_algorithms
-
{ concurrently: 'CONCURRENTLY' }
-
end
-
-
1
class StatementPool < ConnectionAdapters::StatementPool
-
1
def initialize(connection, max)
-
super
-
@counter = 0
-
@cache = Hash.new { |h,pid| h[pid] = {} }
-
end
-
-
1
def each(&block); cache.each(&block); end
-
1
def key?(key); cache.key?(key); end
-
1
def [](key); cache[key]; end
-
1
def length; cache.length; end
-
-
1
def next_key
-
"a#{@counter + 1}"
-
end
-
-
1
def []=(sql, key)
-
while @max <= cache.size
-
dealloc(cache.shift.last)
-
end
-
@counter += 1
-
cache[sql] = key
-
end
-
-
1
def clear
-
cache.each_value do |stmt_key|
-
dealloc stmt_key
-
end
-
cache.clear
-
end
-
-
1
def delete(sql_key)
-
dealloc cache[sql_key]
-
cache.delete sql_key
-
end
-
-
1
private
-
-
1
def cache
-
@cache[Process.pid]
-
end
-
-
1
def dealloc(key)
-
@connection.query "DEALLOCATE #{key}" if connection_active?
-
end
-
-
1
def connection_active?
-
@connection.status == PGconn::CONNECTION_OK
-
rescue PGError
-
false
-
end
-
end
-
-
# Initializes and connects a PostgreSQL adapter.
-
1
def initialize(connection, logger, connection_parameters, config)
-
super(connection, logger)
-
-
@visitor = Arel::Visitors::PostgreSQL.new self
-
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
-
@prepared_statements = true
-
else
-
@prepared_statements = false
-
end
-
-
@connection_parameters, @config = connection_parameters, config
-
-
# @local_tz is initialized as nil to avoid warnings when connect tries to use it
-
@local_tz = nil
-
@table_alias_length = nil
-
-
connect
-
@statements = StatementPool.new @connection,
-
self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })
-
-
if postgresql_version < 80200
-
raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
-
end
-
-
@type_map = Type::HashLookupTypeMap.new
-
initialize_type_map(type_map)
-
@local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
-
@use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
-
end
-
-
# Clears the prepared statements cache.
-
1
def clear_cache!
-
@statements.clear
-
end
-
-
1
def truncate(table_name, name = nil)
-
exec_query "TRUNCATE TABLE #{quote_table_name(table_name)}", name, []
-
end
-
-
# Is this connection alive and ready for queries?
-
1
def active?
-
@connection.query 'SELECT 1'
-
true
-
rescue PGError
-
false
-
end
-
-
# Close then reopen the connection.
-
1
def reconnect!
-
super
-
@connection.reset
-
configure_connection
-
end
-
-
1
def reset!
-
clear_cache!
-
reset_transaction
-
unless @connection.transaction_status == ::PG::PQTRANS_IDLE
-
@connection.query 'ROLLBACK'
-
end
-
@connection.query 'DISCARD ALL'
-
configure_connection
-
end
-
-
# Disconnects from the database if already connected. Otherwise, this
-
# method does nothing.
-
1
def disconnect!
-
super
-
@connection.close rescue nil
-
end
-
-
1
def native_database_types #:nodoc:
-
NATIVE_DATABASE_TYPES
-
end
-
-
# Returns true, since this connection adapter supports migrations.
-
1
def supports_migrations?
-
true
-
end
-
-
# Does PostgreSQL support finding primary key on non-Active Record tables?
-
1
def supports_primary_key? #:nodoc:
-
true
-
end
-
-
1
def set_standard_conforming_strings
-
execute('SET standard_conforming_strings = on', 'SCHEMA')
-
end
-
-
1
def supports_ddl_transactions?
-
true
-
end
-
-
1
def supports_explain?
-
true
-
end
-
-
# Returns true if pg > 9.1
-
1
def supports_extensions?
-
postgresql_version >= 90100
-
end
-
-
# Range datatypes weren't introduced until PostgreSQL 9.2
-
1
def supports_ranges?
-
postgresql_version >= 90200
-
end
-
-
1
def supports_materialized_views?
-
postgresql_version >= 90300
-
end
-
-
1
def enable_extension(name)
-
exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap {
-
reload_type_map
-
}
-
end
-
-
1
def disable_extension(name)
-
exec_query("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE").tap {
-
reload_type_map
-
}
-
end
-
-
1
def extension_enabled?(name)
-
if supports_extensions?
-
res = exec_query "SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled",
-
'SCHEMA'
-
res.cast_values.first
-
end
-
end
-
-
1
def extensions
-
if supports_extensions?
-
exec_query("SELECT extname from pg_extension", "SCHEMA").cast_values
-
else
-
super
-
end
-
end
-
-
# Returns the configured supported identifier length supported by PostgreSQL
-
1
def table_alias_length
-
@table_alias_length ||= query('SHOW max_identifier_length', 'SCHEMA')[0][0].to_i
-
end
-
-
# Set the authorized user for this session
-
1
def session_auth=(user)
-
clear_cache!
-
exec_query "SET SESSION AUTHORIZATION #{user}"
-
end
-
-
1
def use_insert_returning?
-
@use_insert_returning
-
end
-
-
1
def valid_type?(type)
-
!native_database_types[type].nil?
-
end
-
-
1
def update_table_definition(table_name, base) #:nodoc:
-
PostgreSQL::Table.new(table_name, base)
-
end
-
-
1
def lookup_cast_type(sql_type) # :nodoc:
-
oid = execute("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").first['oid'].to_i
-
super(oid)
-
end
-
-
1
def column_name_for_operation(operation, node) # :nodoc:
-
OPERATION_ALIASES.fetch(operation) { operation.downcase }
-
end
-
-
1
OPERATION_ALIASES = { # :nodoc:
-
"maximum" => "max",
-
"minimum" => "min",
-
"average" => "avg",
-
}
-
-
1
protected
-
-
# Returns the version of the connected PostgreSQL server.
-
1
def postgresql_version
-
@connection.server_version
-
end
-
-
# See http://www.postgresql.org/docs/9.1/static/errcodes-appendix.html
-
1
FOREIGN_KEY_VIOLATION = "23503"
-
1
UNIQUE_VIOLATION = "23505"
-
-
1
def translate_exception(exception, message)
-
return exception unless exception.respond_to?(:result)
-
-
case exception.result.try(:error_field, PGresult::PG_DIAG_SQLSTATE)
-
when UNIQUE_VIOLATION
-
RecordNotUnique.new(message, exception)
-
when FOREIGN_KEY_VIOLATION
-
InvalidForeignKey.new(message, exception)
-
else
-
super
-
end
-
end
-
-
1
private
-
-
1
def get_oid_type(oid, fmod, column_name, sql_type = '') # :nodoc:
-
if !type_map.key?(oid)
-
load_additional_types(type_map, [oid])
-
end
-
-
type_map.fetch(oid, fmod, sql_type) {
-
warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
-
Type::Value.new.tap do |cast_type|
-
type_map.register_type(oid, cast_type)
-
end
-
}
-
end
-
-
1
def initialize_type_map(m) # :nodoc:
-
register_class_with_limit m, 'int2', OID::Integer
-
register_class_with_limit m, 'int4', OID::Integer
-
register_class_with_limit m, 'int8', OID::Integer
-
m.alias_type 'oid', 'int2'
-
m.register_type 'float4', OID::Float.new
-
m.alias_type 'float8', 'float4'
-
m.register_type 'text', Type::Text.new
-
register_class_with_limit m, 'varchar', Type::String
-
m.alias_type 'char', 'varchar'
-
m.alias_type 'name', 'varchar'
-
m.alias_type 'bpchar', 'varchar'
-
m.register_type 'bool', Type::Boolean.new
-
register_class_with_limit m, 'bit', OID::Bit
-
register_class_with_limit m, 'varbit', OID::BitVarying
-
m.alias_type 'timestamptz', 'timestamp'
-
m.register_type 'date', OID::Date.new
-
m.register_type 'time', OID::Time.new
-
-
m.register_type 'money', OID::Money.new
-
m.register_type 'bytea', OID::Bytea.new
-
m.register_type 'point', OID::Point.new
-
m.register_type 'hstore', OID::Hstore.new
-
m.register_type 'json', OID::Json.new
-
m.register_type 'jsonb', OID::Jsonb.new
-
m.register_type 'cidr', OID::Cidr.new
-
m.register_type 'inet', OID::Inet.new
-
m.register_type 'uuid', OID::Uuid.new
-
m.register_type 'xml', OID::Xml.new
-
m.register_type 'tsvector', OID::SpecializedString.new(:tsvector)
-
m.register_type 'macaddr', OID::SpecializedString.new(:macaddr)
-
m.register_type 'citext', OID::SpecializedString.new(:citext)
-
m.register_type 'ltree', OID::SpecializedString.new(:ltree)
-
-
# FIXME: why are we keeping these types as strings?
-
m.alias_type 'interval', 'varchar'
-
m.alias_type 'path', 'varchar'
-
m.alias_type 'line', 'varchar'
-
m.alias_type 'polygon', 'varchar'
-
m.alias_type 'circle', 'varchar'
-
m.alias_type 'lseg', 'varchar'
-
m.alias_type 'box', 'varchar'
-
-
m.register_type 'timestamp' do |_, _, sql_type|
-
precision = extract_precision(sql_type)
-
OID::DateTime.new(precision: precision)
-
end
-
-
m.register_type 'numeric' do |_, fmod, sql_type|
-
precision = extract_precision(sql_type)
-
scale = extract_scale(sql_type)
-
-
# The type for the numeric depends on the width of the field,
-
# so we'll do something special here.
-
#
-
# When dealing with decimal columns:
-
#
-
# places after decimal = fmod - 4 & 0xffff
-
# places before decimal = (fmod - 4) >> 16 & 0xffff
-
if fmod && (fmod - 4 & 0xffff).zero?
-
# FIXME: Remove this class, and the second argument to
-
# lookups on PG
-
Type::DecimalWithoutScale.new(precision: precision)
-
else
-
OID::Decimal.new(precision: precision, scale: scale)
-
end
-
end
-
-
load_additional_types(m)
-
end
-
-
1
def extract_limit(sql_type) # :nodoc:
-
case sql_type
-
when /^bigint/i, /^int8/i
-
8
-
when /^smallint/i
-
2
-
else
-
super
-
end
-
end
-
-
# Extracts the value from a PostgreSQL column default definition.
-
1
def extract_value_from_default(oid, default) # :nodoc:
-
case default
-
# Quoted types
-
when /\A[\(B]?'(.*)'::/m
-
$1.gsub(/''/, "'")
-
# Boolean types
-
when 'true', 'false'
-
default
-
# Numeric types
-
when /\A\(?(-?\d+(\.\d*)?)\)?(::bigint)?\z/
-
$1
-
# Object identifier types
-
when /\A-?\d+\z/
-
$1
-
else
-
# Anything else is blank, some user type, or some function
-
# and we can't know the value of that, so return nil.
-
nil
-
end
-
end
-
-
1
def extract_default_function(default_value, default) # :nodoc:
-
default if has_default_function?(default_value, default)
-
end
-
-
1
def has_default_function?(default_value, default) # :nodoc:
-
!default_value && (%r{\w+\(.*\)} === default)
-
end
-
-
1
def load_additional_types(type_map, oids = nil) # :nodoc:
-
initializer = OID::TypeMapInitializer.new(type_map)
-
-
if supports_ranges?
-
query = <<-SQL
-
SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
-
FROM pg_type as t
-
LEFT JOIN pg_range as r ON oid = rngtypid
-
SQL
-
else
-
query = <<-SQL
-
SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, t.typtype, t.typbasetype
-
FROM pg_type as t
-
SQL
-
end
-
-
if oids
-
query += "WHERE t.oid::integer IN (%s)" % oids.join(", ")
-
else
-
query += initializer.query_conditions_for_initial_load(type_map)
-
end
-
-
execute_and_clear(query, 'SCHEMA', []) do |records|
-
initializer.run(records)
-
end
-
end
-
-
1
FEATURE_NOT_SUPPORTED = "0A000" #:nodoc:
-
-
1
def execute_and_clear(sql, name, binds)
-
result = without_prepared_statement?(binds) ? exec_no_cache(sql, name, binds) :
-
exec_cache(sql, name, binds)
-
ret = yield result
-
result.clear
-
ret
-
end
-
-
1
def exec_no_cache(sql, name, binds)
-
log(sql, name, binds) { @connection.async_exec(sql, []) }
-
end
-
-
1
def exec_cache(sql, name, binds)
-
stmt_key = prepare_statement(sql)
-
type_casted_binds = binds.map { |col, val|
-
[col, type_cast(val, col)]
-
}
-
-
log(sql, name, type_casted_binds, stmt_key) do
-
@connection.exec_prepared(stmt_key, type_casted_binds.map { |_, val| val })
-
end
-
rescue ActiveRecord::StatementInvalid => e
-
pgerror = e.original_exception
-
-
# Get the PG code for the failure. Annoyingly, the code for
-
# prepared statements whose return value may have changed is
-
# FEATURE_NOT_SUPPORTED. Check here for more details:
-
# http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
-
begin
-
code = pgerror.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
-
rescue
-
raise e
-
end
-
if FEATURE_NOT_SUPPORTED == code
-
@statements.delete sql_key(sql)
-
retry
-
else
-
raise e
-
end
-
end
-
-
# Returns the statement identifier for the client side cache
-
# of statements
-
1
def sql_key(sql)
-
"#{schema_search_path}-#{sql}"
-
end
-
-
# Prepare the statement if it hasn't been prepared, return
-
# the statement key.
-
1
def prepare_statement(sql)
-
sql_key = sql_key(sql)
-
unless @statements.key? sql_key
-
nextkey = @statements.next_key
-
begin
-
@connection.prepare nextkey, sql
-
rescue => e
-
raise translate_exception_class(e, sql)
-
end
-
# Clear the queue
-
@connection.get_last_result
-
@statements[sql_key] = nextkey
-
end
-
@statements[sql_key]
-
end
-
-
# Connects to a PostgreSQL server and sets up the adapter depending on the
-
# connected server's characteristics.
-
1
def connect
-
@connection = PGconn.connect(@connection_parameters)
-
-
# Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
-
# PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
-
# should know about this but can't detect it there, so deal with it here.
-
OID::Money.precision = (postgresql_version >= 80300) ? 19 : 10
-
-
configure_connection
-
rescue ::PG::Error => error
-
if error.message.include?("does not exist")
-
raise ActiveRecord::NoDatabaseError.new(error.message, error)
-
else
-
raise
-
end
-
end
-
-
# Configures the encoding, verbosity, schema search path, and time zone of the connection.
-
# This is called by #connect and should not be called manually.
-
1
def configure_connection
-
if @config[:encoding]
-
@connection.set_client_encoding(@config[:encoding])
-
end
-
self.client_min_messages = @config[:min_messages] || 'warning'
-
self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
-
-
# Use standard-conforming strings so we don't have to do the E'...' dance.
-
set_standard_conforming_strings
-
-
# If using Active Record's time zone support configure the connection to return
-
# TIMESTAMP WITH ZONE types in UTC.
-
# (SET TIME ZONE does not use an equals sign like other SET variables)
-
if ActiveRecord::Base.default_timezone == :utc
-
execute("SET time zone 'UTC'", 'SCHEMA')
-
elsif @local_tz
-
execute("SET time zone '#{@local_tz}'", 'SCHEMA')
-
end
-
-
# SET statements from :variables config hash
-
# http://www.postgresql.org/docs/8.3/static/sql-set.html
-
variables = @config[:variables] || {}
-
variables.map do |k, v|
-
if v == ':default' || v == :default
-
# Sets the value to the global or compile default
-
execute("SET SESSION #{k} TO DEFAULT", 'SCHEMA')
-
elsif !v.nil?
-
execute("SET SESSION #{k} TO #{quote(v)}", 'SCHEMA')
-
end
-
end
-
end
-
-
# Returns the current ID of a table's sequence.
-
1
def last_insert_id(sequence_name) #:nodoc:
-
Integer(last_insert_id_value(sequence_name))
-
end
-
-
1
def last_insert_id_value(sequence_name)
-
last_insert_id_result(sequence_name).rows.first.first
-
end
-
-
1
def last_insert_id_result(sequence_name) #:nodoc:
-
exec_query("SELECT currval('#{sequence_name}')", 'SQL')
-
end
-
-
# Returns the list of a table's column names, data types, and default values.
-
#
-
# The underlying query is roughly:
-
# SELECT column.name, column.type, default.value
-
# FROM column LEFT JOIN default
-
# ON column.table_id = default.table_id
-
# AND column.num = default.column_num
-
# WHERE column.table_id = get_table_id('table_name')
-
# AND column.num > 0
-
# AND NOT column.is_dropped
-
# ORDER BY column.num
-
#
-
# If the table name is not prefixed with a schema, the database will
-
# take the first match from the schema search path.
-
#
-
# Query implementation notes:
-
# - format_type includes the column size constraint, e.g. varchar(50)
-
# - ::regclass is a function that gives the id for a table name
-
1
def column_definitions(table_name) # :nodoc:
-
exec_query(<<-end_sql, 'SCHEMA').rows
-
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
-
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
-
FROM pg_attribute a LEFT JOIN pg_attrdef d
-
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
-
WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
-
AND a.attnum > 0 AND NOT a.attisdropped
-
ORDER BY a.attnum
-
end_sql
-
end
-
-
1
def extract_table_ref_from_insert_sql(sql) # :nodoc:
-
sql[/into\s+([^\(]*).*values\s*\(/im]
-
$1.strip if $1
-
end
-
-
1
def create_table_definition(name, temporary, options, as = nil) # :nodoc:
-
PostgreSQL::TableDefinition.new native_database_types, name, temporary, options, as
-
end
-
end
-
end
-
end
-
1
require 'active_record/connection_adapters/abstract_adapter'
-
1
require 'active_record/connection_adapters/statement_pool'
-
1
require 'arel/visitors/bind_visitor'
-
-
1
gem 'sqlite3', '~> 1.3.6'
-
1
require 'sqlite3'
-
-
1
module ActiveRecord
-
1
module ConnectionHandling # :nodoc:
-
# sqlite3 adapter reuses sqlite_connection.
-
1
def sqlite3_connection(config)
-
# Require database.
-
2
unless config[:database]
-
raise ArgumentError, "No database file specified. Missing argument: database"
-
end
-
-
# Allow database path relative to Rails.root, but only if the database
-
# path is not the special path that tells sqlite to build a database only
-
# in memory.
-
2
if ':memory:' != config[:database]
-
2
config[:database] = File.expand_path(config[:database], Rails.root) if defined?(Rails.root)
-
2
dirname = File.dirname(config[:database])
-
2
Dir.mkdir(dirname) unless File.directory?(dirname)
-
end
-
-
2
db = SQLite3::Database.new(
-
config[:database].to_s,
-
:results_as_hash => true
-
)
-
-
2
db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout]
-
-
2
ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config)
-
rescue Errno::ENOENT => error
-
if error.message.include?("No such file or directory")
-
raise ActiveRecord::NoDatabaseError.new(error.message, error)
-
else
-
raise
-
end
-
end
-
end
-
-
1
module ConnectionAdapters #:nodoc:
-
1
class SQLite3Binary < Type::Binary # :nodoc:
-
1
def cast_value(value)
-
if value.encoding != Encoding::ASCII_8BIT
-
value = value.force_encoding(Encoding::ASCII_8BIT)
-
end
-
value
-
end
-
end
-
-
# The SQLite3 adapter works SQLite 3.6.16 or newer
-
# with the sqlite3-ruby drivers (available as gem from https://rubygems.org/gems/sqlite3).
-
#
-
# Options:
-
#
-
# * <tt>:database</tt> - Path to the database file.
-
1
class SQLite3Adapter < AbstractAdapter
-
1
ADAPTER_NAME = 'SQLite'.freeze
-
1
include Savepoints
-
-
1
NATIVE_DATABASE_TYPES = {
-
primary_key: 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL',
-
string: { name: "varchar" },
-
text: { name: "text" },
-
integer: { name: "integer" },
-
float: { name: "float" },
-
decimal: { name: "decimal" },
-
datetime: { name: "datetime" },
-
time: { name: "time" },
-
date: { name: "date" },
-
binary: { name: "blob" },
-
boolean: { name: "boolean" }
-
}
-
-
1
class Version
-
1
include Comparable
-
-
1
def initialize(version_string)
-
@version = version_string.split('.').map { |v| v.to_i }
-
end
-
-
1
def <=>(version_string)
-
@version <=> version_string.split('.').map { |v| v.to_i }
-
end
-
end
-
-
1
class StatementPool < ConnectionAdapters::StatementPool
-
1
def initialize(connection, max)
-
2
super
-
4
@cache = Hash.new { |h,pid| h[pid] = {} }
-
end
-
-
1
def each(&block); cache.each(&block); end
-
1
def key?(key); cache.key?(key); end
-
85
def [](key); cache[key]; end
-
1
def length; cache.length; end
-
-
1
def []=(sql, key)
-
17
while @max <= cache.size
-
dealloc(cache.shift.last[:stmt])
-
end
-
17
cache[sql] = key
-
end
-
-
1
def clear
-
1
cache.each_value do |hash|
-
dealloc hash[:stmt]
-
end
-
1
cache.clear
-
end
-
-
1
private
-
1
def cache
-
120
@cache[$$]
-
end
-
-
1
def dealloc(stmt)
-
stmt.close unless stmt.closed?
-
end
-
end
-
-
1
def initialize(connection, logger, connection_options, config)
-
2
super(connection, logger)
-
-
2
@active = nil
-
2
@statements = StatementPool.new(@connection,
-
2
self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
-
2
@config = config
-
-
2
@visitor = Arel::Visitors::SQLite.new self
-
-
4
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
-
2
@prepared_statements = true
-
else
-
@prepared_statements = false
-
end
-
end
-
-
1
def supports_ddl_transactions?
-
true
-
end
-
-
1
def supports_savepoints?
-
true
-
end
-
-
1
def supports_partial_index?
-
sqlite_version >= '3.8.0'
-
end
-
-
# Returns true, since this connection adapter supports prepared statement
-
# caching.
-
1
def supports_statement_cache?
-
true
-
end
-
-
# Returns true, since this connection adapter supports migrations.
-
1
def supports_migrations? #:nodoc:
-
true
-
end
-
-
1
def supports_primary_key? #:nodoc:
-
true
-
end
-
-
1
def requires_reloading?
-
true
-
end
-
-
1
def supports_views?
-
true
-
end
-
-
1
def active?
-
29
@active != false
-
end
-
-
# Disconnects from the database if already connected. Otherwise, this
-
# method does nothing.
-
1
def disconnect!
-
1
super
-
1
@active = false
-
1
@connection.close rescue nil
-
end
-
-
# Clears the prepared statements cache.
-
1
def clear_cache!
-
1
@statements.clear
-
end
-
-
1
def supports_index_sort_order?
-
true
-
end
-
-
# Returns 62. SQLite supports index names up to 64
-
# characters. The rest is used by rails internally to perform
-
# temporary rename operations
-
1
def allowed_index_name_length
-
index_name_length - 2
-
end
-
-
1
def native_database_types #:nodoc:
-
NATIVE_DATABASE_TYPES
-
end
-
-
# Returns the current database encoding format as a string, eg: 'UTF-8'
-
1
def encoding
-
@connection.encoding.to_s
-
end
-
-
1
def supports_explain?
-
true
-
end
-
-
# QUOTING ==================================================
-
-
1
def _quote(value) # :nodoc:
-
37
case value
-
when Type::Binary::Data
-
"x'#{value.hex}'"
-
else
-
37
super
-
end
-
end
-
-
1
def _type_cast(value) # :nodoc:
-
471
case value
-
when BigDecimal
-
value.to_f
-
when String
-
319
if value.encoding == Encoding::ASCII_8BIT
-
23
super(value.encode(Encoding::UTF_8))
-
else
-
296
super
-
end
-
else
-
152
super
-
end
-
end
-
-
1
def quote_string(s) #:nodoc:
-
36
@connection.class.quote(s)
-
end
-
-
1
def quote_table_name_for_assignment(table, attr)
-
quote_column_name(attr)
-
end
-
-
1
def quote_column_name(name) #:nodoc:
-
779
%Q("#{name.to_s.gsub('"', '""')}")
-
end
-
-
# Quote date/time values for use in SQL input. Includes microseconds
-
# if the value is a Time responding to usec.
-
1
def quoted_date(value) #:nodoc:
-
68
if value.respond_to?(:usec)
-
63
"#{super}.#{sprintf("%06d", value.usec)}"
-
else
-
5
super
-
end
-
end
-
-
#--
-
# DATABASE STATEMENTS ======================================
-
#++
-
-
1
def explain(arel, binds = [])
-
sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
-
ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', []))
-
end
-
-
1
class ExplainPrettyPrinter
-
# Pretty prints the result of a EXPLAIN QUERY PLAN in a way that resembles
-
# the output of the SQLite shell:
-
#
-
# 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
-
# 0|1|1|SCAN TABLE posts (~100000 rows)
-
#
-
1
def pp(result) # :nodoc:
-
result.rows.map do |row|
-
row.join('|')
-
end.join("\n") + "\n"
-
end
-
end
-
-
1
def exec_query(sql, name = nil, binds = [])
-
181
type_casted_binds = binds.map { |col, val|
-
435
[col, type_cast(val, col)]
-
}
-
-
181
log(sql, name, type_casted_binds) do
-
# Don't cache statements if they are not prepared
-
181
if without_prepared_statement?(binds)
-
97
stmt = @connection.prepare(sql)
-
97
begin
-
97
cols = stmt.columns
-
97
records = stmt.to_a
-
ensure
-
97
stmt.close
-
end
-
97
stmt = records
-
else
-
84
cache = @statements[sql] ||= {
-
:stmt => @connection.prepare(sql)
-
}
-
84
stmt = cache[:stmt]
-
84
cols = cache[:cols] ||= stmt.columns
-
84
stmt.reset!
-
519
stmt.bind_params type_casted_binds.map { |_, val| val }
-
end
-
-
181
ActiveRecord::Result.new(cols, stmt.to_a)
-
end
-
end
-
-
1
def exec_delete(sql, name = 'SQL', binds = [])
-
3
exec_query(sql, name, binds)
-
3
@connection.changes
-
end
-
1
alias :exec_update :exec_delete
-
-
1
def last_inserted_id(result)
-
28
@connection.last_insert_row_id
-
end
-
-
1
def execute(sql, name = nil) #:nodoc:
-
324
log(sql, name) { @connection.execute(sql) }
-
end
-
-
1
def update_sql(sql, name = nil) #:nodoc:
-
super
-
@connection.changes
-
end
-
-
1
def delete_sql(sql, name = nil) #:nodoc:
-
sql += " WHERE 1=1" unless sql =~ /WHERE/i
-
super sql, name
-
end
-
-
1
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
-
super
-
id_value || @connection.last_insert_row_id
-
end
-
1
alias :create :insert_sql
-
-
1
def select_rows(sql, name = nil, binds = [])
-
exec_query(sql, name, binds).rows
-
end
-
-
1
def begin_db_transaction #:nodoc:
-
56
log('begin transaction',nil) { @connection.transaction }
-
end
-
-
1
def commit_db_transaction #:nodoc:
-
log('commit transaction',nil) { @connection.commit }
-
end
-
-
1
def exec_rollback_db_transaction #:nodoc:
-
56
log('rollback transaction',nil) { @connection.rollback }
-
end
-
-
# SCHEMA STATEMENTS ========================================
-
-
1
def tables(name = nil, table_name = nil) #:nodoc:
-
5
sql = <<-SQL
-
SELECT name
-
FROM sqlite_master
-
WHERE (type = 'table' OR type = 'view') AND NOT name = 'sqlite_sequence'
-
SQL
-
5
sql << " AND name = #{quote_table_name(table_name)}" if table_name
-
-
5
exec_query(sql, 'SCHEMA').map do |row|
-
14
row['name']
-
end
-
end
-
-
1
def table_exists?(table_name)
-
2
table_name && tables(nil, table_name).any?
-
end
-
-
# Returns an array of +Column+ objects for the table specified by +table_name+.
-
1
def columns(table_name) #:nodoc:
-
4
table_structure(table_name).map do |field|
-
35
case field["dflt_value"]
-
when /^null$/i
-
field["dflt_value"] = nil
-
when /^'(.*)'$/m
-
2
field["dflt_value"] = $1.gsub("''", "'")
-
when /^"(.*)"$/m
-
field["dflt_value"] = $1.gsub('""', '"')
-
end
-
-
35
sql_type = field['type']
-
35
cast_type = lookup_cast_type(sql_type)
-
35
new_column(field['name'], field['dflt_value'], cast_type, sql_type, field['notnull'].to_i == 0)
-
end
-
end
-
-
# Returns an array of indexes for the given table.
-
1
def indexes(table_name, name = nil) #:nodoc:
-
exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", 'SCHEMA').map do |row|
-
sql = <<-SQL
-
SELECT sql
-
FROM sqlite_master
-
WHERE name=#{quote(row['name'])} AND type='index'
-
UNION ALL
-
SELECT sql
-
FROM sqlite_temp_master
-
WHERE name=#{quote(row['name'])} AND type='index'
-
SQL
-
index_sql = exec_query(sql).first['sql']
-
match = /\sWHERE\s+(.+)$/i.match(index_sql)
-
where = match[1] if match
-
IndexDefinition.new(
-
table_name,
-
row['name'],
-
row['unique'] != 0,
-
exec_query("PRAGMA index_info('#{row['name']}')", "SCHEMA").map { |col|
-
col['name']
-
}, nil, nil, where)
-
end
-
end
-
-
1
def primary_key(table_name) #:nodoc:
-
37
pks = table_structure(table_name).select { |f| f['pk'] > 0 }
-
3
return nil unless pks.count == 1
-
3
pks[0]['name']
-
end
-
-
1
def remove_index!(table_name, index_name) #:nodoc:
-
exec_query "DROP INDEX #{quote_column_name(index_name)}"
-
end
-
-
# Renames a table.
-
#
-
# Example:
-
# rename_table('octopuses', 'octopi')
-
1
def rename_table(table_name, new_name)
-
exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
-
rename_table_indexes(table_name, new_name)
-
end
-
-
# See: http://www.sqlite.org/lang_altertable.html
-
# SQLite has an additional restriction on the ALTER TABLE statement
-
1
def valid_alter_table_type?(type)
-
type.to_sym != :primary_key
-
end
-
-
1
def add_column(table_name, column_name, type, options = {}) #:nodoc:
-
if valid_alter_table_type?(type)
-
super(table_name, column_name, type, options)
-
else
-
alter_table(table_name) do |definition|
-
definition.column(column_name, type, options)
-
end
-
end
-
end
-
-
1
def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
-
alter_table(table_name) do |definition|
-
definition.remove_column column_name
-
end
-
end
-
-
1
def change_column_default(table_name, column_name, default) #:nodoc:
-
alter_table(table_name) do |definition|
-
definition[column_name].default = default
-
end
-
end
-
-
1
def change_column_null(table_name, column_name, null, default = nil)
-
unless null || default.nil?
-
exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
-
end
-
alter_table(table_name) do |definition|
-
definition[column_name].null = null
-
end
-
end
-
-
1
def change_column(table_name, column_name, type, options = {}) #:nodoc:
-
alter_table(table_name) do |definition|
-
include_default = options_include_default?(options)
-
definition[column_name].instance_eval do
-
self.type = type
-
self.limit = options[:limit] if options.include?(:limit)
-
self.default = options[:default] if include_default
-
self.null = options[:null] if options.include?(:null)
-
self.precision = options[:precision] if options.include?(:precision)
-
self.scale = options[:scale] if options.include?(:scale)
-
end
-
end
-
end
-
-
1
def rename_column(table_name, column_name, new_column_name) #:nodoc:
-
column = column_for(table_name, column_name)
-
alter_table(table_name, rename: {column.name => new_column_name.to_s})
-
rename_column_indexes(table_name, column.name, new_column_name)
-
end
-
-
1
protected
-
-
1
def initialize_type_map(m)
-
2
super
-
2
m.register_type(/binary/i, SQLite3Binary.new)
-
end
-
-
1
def table_structure(table_name)
-
7
structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA').to_hash
-
7
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
-
7
structure
-
end
-
-
1
def alter_table(table_name, options = {}) #:nodoc:
-
altered_table_name = "a#{table_name}"
-
caller = lambda {|definition| yield definition if block_given?}
-
-
transaction do
-
move_table(table_name, altered_table_name,
-
options.merge(:temporary => true))
-
move_table(altered_table_name, table_name, &caller)
-
end
-
end
-
-
1
def move_table(from, to, options = {}, &block) #:nodoc:
-
copy_table(from, to, options, &block)
-
drop_table(from)
-
end
-
-
1
def copy_table(from, to, options = {}) #:nodoc:
-
from_primary_key = primary_key(from)
-
options[:id] = false
-
create_table(to, options) do |definition|
-
@definition = definition
-
@definition.primary_key(from_primary_key) if from_primary_key.present?
-
columns(from).each do |column|
-
column_name = options[:rename] ?
-
(options[:rename][column.name] ||
-
options[:rename][column.name.to_sym] ||
-
column.name) : column.name
-
next if column_name == from_primary_key
-
-
@definition.column(column_name, column.type,
-
:limit => column.limit, :default => column.default,
-
:precision => column.precision, :scale => column.scale,
-
:null => column.null)
-
end
-
yield @definition if block_given?
-
end
-
copy_table_indexes(from, to, options[:rename] || {})
-
copy_table_contents(from, to,
-
@definition.columns.map {|column| column.name},
-
options[:rename] || {})
-
end
-
-
1
def copy_table_indexes(from, to, rename = {}) #:nodoc:
-
indexes(from).each do |index|
-
name = index.name
-
if to == "a#{from}"
-
name = "t#{name}"
-
elsif from == "a#{to}"
-
name = name[1..-1]
-
end
-
-
to_column_names = columns(to).map { |c| c.name }
-
columns = index.columns.map {|c| rename[c] || c }.select do |column|
-
to_column_names.include?(column)
-
end
-
-
unless columns.empty?
-
# index name can't be the same
-
opts = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true }
-
opts[:unique] = true if index.unique
-
add_index(to, columns, opts)
-
end
-
end
-
end
-
-
1
def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
-
column_mappings = Hash[columns.map {|name| [name, name]}]
-
rename.each { |a| column_mappings[a.last] = a.first }
-
from_columns = columns(from).collect {|col| col.name}
-
columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
-
quoted_columns = columns.map { |col| quote_column_name(col) } * ','
-
-
quoted_to = quote_table_name(to)
-
-
raw_column_mappings = Hash[columns(from).map { |c| [c.name, c] }]
-
-
exec_query("SELECT * FROM #{quote_table_name(from)}").each do |row|
-
sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES ("
-
-
column_values = columns.map do |col|
-
quote(row[column_mappings[col]], raw_column_mappings[col])
-
end
-
-
sql << column_values * ', '
-
sql << ')'
-
exec_query sql
-
end
-
end
-
-
1
def sqlite_version
-
@sqlite_version ||= SQLite3Adapter::Version.new(select_value('select sqlite_version(*)'))
-
end
-
-
1
def translate_exception(exception, message)
-
case exception.message
-
# SQLite 3.8.2 returns a newly formatted error message:
-
# UNIQUE constraint failed: *table_name*.*column_name*
-
# Older versions of SQLite return:
-
# column *column_name* is not unique
-
when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/
-
RecordNotUnique.new(message, exception)
-
else
-
super
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
class StatementPool
-
1
include Enumerable
-
-
1
def initialize(connection, max = 1000)
-
2
@connection = connection
-
2
@max = max
-
end
-
-
1
def each
-
raise NotImplementedError
-
end
-
-
1
def key?(key)
-
raise NotImplementedError
-
end
-
-
1
def [](key)
-
raise NotImplementedError
-
end
-
-
1
def length
-
raise NotImplementedError
-
end
-
-
1
def []=(sql, key)
-
raise NotImplementedError
-
end
-
-
1
def clear
-
raise NotImplementedError
-
end
-
-
1
def delete(key)
-
raise NotImplementedError
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionHandling
-
4
RAILS_ENV = -> { (Rails.env if defined?(Rails.env)) || ENV["RAILS_ENV"] || ENV["RACK_ENV"] }
-
4
DEFAULT_ENV = -> { RAILS_ENV.call || "default_env" }
-
-
# Establishes the connection to the database. Accepts a hash as input where
-
# the <tt>:adapter</tt> key must be specified with the name of a database adapter (in lower-case)
-
# example for regular databases (MySQL, Postgresql, etc):
-
#
-
# ActiveRecord::Base.establish_connection(
-
# adapter: "mysql",
-
# host: "localhost",
-
# username: "myuser",
-
# password: "mypass",
-
# database: "somedatabase"
-
# )
-
#
-
# Example for SQLite database:
-
#
-
# ActiveRecord::Base.establish_connection(
-
# adapter: "sqlite3",
-
# database: "path/to/dbfile"
-
# )
-
#
-
# Also accepts keys as strings (for parsing from YAML for example):
-
#
-
# ActiveRecord::Base.establish_connection(
-
# "adapter" => "sqlite3",
-
# "database" => "path/to/dbfile"
-
# )
-
#
-
# Or a URL:
-
#
-
# ActiveRecord::Base.establish_connection(
-
# "postgres://myuser:mypass@localhost/somedatabase"
-
# )
-
#
-
# In case <tt>ActiveRecord::Base.configurations</tt> is set (Rails
-
# automatically loads the contents of config/database.yml into it),
-
# a symbol can also be given as argument, representing a key in the
-
# configuration hash:
-
#
-
# ActiveRecord::Base.establish_connection(:production)
-
#
-
# The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError
-
# may be returned on an error.
-
1
def establish_connection(spec = nil)
-
2
spec ||= DEFAULT_ENV.call.to_sym
-
2
resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new configurations
-
2
spec = resolver.spec(spec)
-
-
2
unless respond_to?(spec.adapter_method)
-
raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
-
end
-
-
2
remove_connection
-
2
connection_handler.establish_connection self, spec
-
end
-
-
1
class MergeAndResolveDefaultUrlConfig # :nodoc:
-
1
def initialize(raw_configurations)
-
2
@raw_config = raw_configurations.dup
-
2
@env = DEFAULT_ENV.call.to_s
-
end
-
-
# Returns fully resolved connection hashes.
-
# Merges connection information from `ENV['DATABASE_URL']` if available.
-
1
def resolve
-
2
ConnectionAdapters::ConnectionSpecification::Resolver.new(config).resolve_all
-
end
-
-
1
private
-
1
def config
-
2
@raw_config.dup.tap do |cfg|
-
2
if url = ENV['DATABASE_URL']
-
cfg[@env] ||= {}
-
cfg[@env]["url"] ||= url
-
end
-
end
-
end
-
end
-
-
# Returns the connection currently associated with the class. This can
-
# also be used to "borrow" the connection to do database work unrelated
-
# to any of the specific Active Records.
-
1
def connection
-
907
retrieve_connection
-
end
-
-
1
def connection_id
-
992
ActiveRecord::RuntimeRegistry.connection_id
-
end
-
-
1
def connection_id=(connection_id)
-
1
ActiveRecord::RuntimeRegistry.connection_id = connection_id
-
end
-
-
# Returns the configuration of the associated connection as a hash:
-
#
-
# ActiveRecord::Base.connection_config
-
# # => {pool: 5, timeout: 5000, database: "db/development.sqlite3", adapter: "sqlite3"}
-
#
-
# Please use only for reading.
-
1
def connection_config
-
1
connection_pool.spec.config
-
end
-
-
1
def connection_pool
-
1
connection_handler.retrieve_connection_pool(self) or raise ConnectionNotEstablished
-
end
-
-
1
def retrieve_connection
-
907
connection_handler.retrieve_connection(self)
-
end
-
-
# Returns +true+ if Active Record is connected.
-
1
def connected?
-
25
connection_handler.connected?(self)
-
end
-
-
1
def remove_connection(klass = self)
-
2
connection_handler.remove_connection(klass)
-
end
-
-
1
def clear_cache! # :nodoc:
-
connection.schema_cache.clear!
-
end
-
-
1
delegate :clear_active_connections!, :clear_reloadable_connections!,
-
:clear_all_connections!, :to => :connection_handler
-
end
-
end
-
1
require 'thread'
-
1
require 'active_support/core_ext/hash/indifferent_access'
-
1
require 'active_support/core_ext/object/duplicable'
-
1
require 'active_support/core_ext/string/filters'
-
-
1
module ActiveRecord
-
1
module Core
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
##
-
# :singleton-method:
-
#
-
# Accepts a logger conforming to the interface of Log4r which is then
-
# passed on to any new database connections made and which can be
-
# retrieved on both a class and instance level by calling +logger+.
-
1
mattr_accessor :logger, instance_writer: false
-
-
##
-
# Contains the database configuration - as is typically stored in config/database.yml -
-
# as a Hash.
-
#
-
# For example, the following database.yml...
-
#
-
# development:
-
# adapter: sqlite3
-
# database: db/development.sqlite3
-
#
-
# production:
-
# adapter: sqlite3
-
# database: db/production.sqlite3
-
#
-
# ...would result in ActiveRecord::Base.configurations to look like this:
-
#
-
# {
-
# 'development' => {
-
# 'adapter' => 'sqlite3',
-
# 'database' => 'db/development.sqlite3'
-
# },
-
# 'production' => {
-
# 'adapter' => 'sqlite3',
-
# 'database' => 'db/production.sqlite3'
-
# }
-
# }
-
1
def self.configurations=(config)
-
2
@@configurations = ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig.new(config).resolve
-
end
-
1
self.configurations = {}
-
-
# Returns fully resolved configurations hash
-
1
def self.configurations
-
2
@@configurations
-
end
-
-
##
-
# :singleton-method:
-
# Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
-
# dates and times from the database. This is set to :utc by default.
-
1
mattr_accessor :default_timezone, instance_writer: false
-
1
self.default_timezone = :utc
-
-
##
-
# :singleton-method:
-
# Specifies the format to use when dumping the database schema with Rails'
-
# Rakefile. If :sql, the schema is dumped as (potentially database-
-
# specific) SQL statements. If :ruby, the schema is dumped as an
-
# ActiveRecord::Schema file which can be loaded into any database that
-
# supports migrations. Use :ruby if you want to have different database
-
# adapters for, e.g., your development and test environments.
-
1
mattr_accessor :schema_format, instance_writer: false
-
1
self.schema_format = :ruby
-
-
##
-
# :singleton-method:
-
# Specify whether or not to use timestamps for migration versions
-
1
mattr_accessor :timestamped_migrations, instance_writer: false
-
1
self.timestamped_migrations = true
-
-
##
-
# :singleton-method:
-
# Specify whether schema dump should happen at the end of the
-
# db:migrate rake task. This is true by default, which is useful for the
-
# development environment. This should ideally be false in the production
-
# environment where dumping schema is rarely needed.
-
1
mattr_accessor :dump_schema_after_migration, instance_writer: false
-
1
self.dump_schema_after_migration = true
-
-
1
mattr_accessor :maintain_test_schema, instance_accessor: false
-
-
1
def self.disable_implicit_join_references=(value)
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
Implicit join references were removed with Rails 4.1.
-
Make sure to remove this configuration because it does nothing.
-
MSG
-
end
-
-
1
class_attribute :default_connection_handler, instance_writer: false
-
1
class_attribute :find_by_statement_cache
-
-
1
def self.connection_handler
-
998
ActiveRecord::RuntimeRegistry.connection_handler || default_connection_handler
-
end
-
-
1
def self.connection_handler=(handler)
-
ActiveRecord::RuntimeRegistry.connection_handler = handler
-
end
-
-
1
self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
-
end
-
-
1
module ClassMethods
-
1
def allocate
-
27
define_attribute_methods
-
27
super
-
end
-
-
1
def initialize_find_by_cache # :nodoc:
-
4
self.find_by_statement_cache = {}.extend(Mutex_m)
-
end
-
-
1
def inherited(child_class) # :nodoc:
-
4
child_class.initialize_find_by_cache
-
4
super
-
end
-
-
1
def find(*ids) # :nodoc:
-
# We don't have cache keys for this stuff yet
-
16
return super unless ids.length == 1
-
# Allow symbols to super to maintain compatibility for deprecated finders until Rails 5
-
16
return super if ids.first.kind_of?(Symbol)
-
return super if block_given? ||
-
16
primary_key.nil? ||
-
default_scopes.any? ||
-
current_scope ||
-
columns_hash.include?(inheritance_column) ||
-
ids.first.kind_of?(Array)
-
-
16
id = ids.first
-
16
if ActiveRecord::Base === id
-
id = id.id
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
You are passing an instance of ActiveRecord::Base to `find`.
-
Please pass the id of the object by calling `.id`
-
MSG
-
end
-
16
key = primary_key
-
-
16
s = find_by_statement_cache[key] || find_by_statement_cache.synchronize {
-
2
find_by_statement_cache[key] ||= StatementCache.create(connection) { |params|
-
2
where(key => params.bind).limit(1)
-
}
-
}
-
16
record = s.execute([id], self, connection).first
-
16
unless record
-
raise RecordNotFound, "Couldn't find #{name} with '#{primary_key}'=#{id}"
-
end
-
16
record
-
rescue RangeError
-
raise RecordNotFound, "Couldn't find #{name} with an out of range value for '#{primary_key}'"
-
end
-
-
1
def find_by(*args) # :nodoc:
-
8
return super if current_scope || !(Hash === args.first) || reflect_on_all_aggregations.any?
-
8
return super if default_scopes.any?
-
-
8
hash = args.first
-
-
8
return super if hash.values.any? { |v|
-
8
v.nil? || Array === v || Hash === v
-
}
-
-
# We can't cache Post.find_by(author: david) ...yet
-
16
return super unless hash.keys.all? { |k| columns_hash.has_key?(k.to_s) }
-
-
8
key = hash.keys
-
-
8
klass = self
-
8
s = find_by_statement_cache[key] || find_by_statement_cache.synchronize {
-
2
find_by_statement_cache[key] ||= StatementCache.create(connection) { |params|
-
2
wheres = key.each_with_object({}) { |param,o|
-
2
o[param] = params.bind
-
}
-
2
klass.where(wheres).limit(1)
-
}
-
}
-
8
begin
-
8
s.execute(hash.values, self, connection).first
-
rescue TypeError => e
-
raise ActiveRecord::StatementInvalid.new(e.message, e)
-
rescue RangeError
-
nil
-
end
-
end
-
-
1
def find_by!(*args) # :nodoc:
-
find_by(*args) or raise RecordNotFound.new("Couldn't find #{name}")
-
end
-
-
1
def initialize_generated_modules # :nodoc:
-
5
generated_association_methods
-
end
-
-
1
def generated_association_methods
-
@generated_association_methods ||= begin
-
5
mod = const_set(:GeneratedAssociationMethods, Module.new)
-
5
include mod
-
5
mod
-
8
end
-
end
-
-
# Returns a string like 'Post(id:integer, title:string, body:text)'
-
1
def inspect
-
if self == Base
-
super
-
elsif abstract_class?
-
"#{super}(abstract)"
-
elsif !connected?
-
"#{super} (call '#{super}.connection' to establish a connection)"
-
elsif table_exists?
-
attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', '
-
"#{super}(#{attr_list})"
-
else
-
"#{super}(Table doesn't exist)"
-
end
-
end
-
-
# Overwrite the default class equality method to provide support for association proxies.
-
1
def ===(object)
-
244
object.is_a?(self)
-
end
-
-
# Returns an instance of <tt>Arel::Table</tt> loaded with the current table name.
-
#
-
# class Post < ActiveRecord::Base
-
# scope :published_and_commented, -> { published.and(self.arel_table[:comments_count].gt(0)) }
-
# end
-
1
def arel_table # :nodoc:
-
326
@arel_table ||= Arel::Table.new(table_name, arel_engine)
-
end
-
-
# Returns the Arel engine.
-
1
def arel_engine # :nodoc:
-
@arel_engine ||=
-
if Base == self || connection_handler.retrieve_connection_pool(self)
-
4
self
-
else
-
superclass.arel_engine
-
4
end
-
end
-
-
1
private
-
-
1
def relation #:nodoc:
-
183
relation = Relation.create(self, arel_table)
-
-
183
if finder_needs_type_condition?
-
relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name)
-
else
-
183
relation
-
end
-
end
-
end
-
-
# New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
-
# attributes but not yet saved (pass a hash with key names matching the associated table column names).
-
# In both instances, valid attribute keys are determined by the column names of the associated table --
-
# hence you can't have attributes that aren't part of the table columns.
-
#
-
# ==== Example:
-
# # Instantiates a single new object
-
# User.new(first_name: 'Jamie')
-
1
def initialize(attributes = nil, options = {})
-
43
@attributes = self.class._default_attributes.dup
-
43
self.class.define_attribute_methods
-
-
43
init_internals
-
43
initialize_internals_callback
-
-
# +options+ argument is only needed to make protected_attributes gem easier to hook.
-
# Remove it when we drop support to this gem.
-
43
init_attributes(attributes, options) if attributes
-
-
43
yield self if block_given?
-
43
_run_initialize_callbacks
-
end
-
-
# Initialize an empty model object from +coder+. +coder+ should be
-
# the result of previously encoding an Active Record model, using
-
# `encode_with`
-
#
-
# class Post < ActiveRecord::Base
-
# end
-
#
-
# old_post = Post.new(title: "hello world")
-
# coder = {}
-
# old_post.encode_with(coder)
-
#
-
# post = Post.allocate
-
# post.init_with(coder)
-
# post.title # => 'hello world'
-
1
def init_with(coder)
-
27
coder = LegacyYamlAdapter.convert(self.class, coder)
-
27
@attributes = coder['attributes']
-
-
27
init_internals
-
-
27
@new_record = coder['new_record']
-
-
27
self.class.define_attribute_methods
-
-
27
_run_find_callbacks
-
27
_run_initialize_callbacks
-
-
27
self
-
end
-
-
##
-
# :method: clone
-
# Identical to Ruby's clone method. This is a "shallow" copy. Be warned that your attributes are not copied.
-
# That means that modifying attributes of the clone will modify the original, since they will both point to the
-
# same attributes hash. If you need a copy of your attributes hash, please use the #dup method.
-
#
-
# user = User.first
-
# new_user = user.clone
-
# user.name # => "Bob"
-
# new_user.name = "Joe"
-
# user.name # => "Joe"
-
#
-
# user.object_id == new_user.object_id # => false
-
# user.name.object_id == new_user.name.object_id # => true
-
#
-
# user.name.object_id == user.dup.name.object_id # => false
-
-
##
-
# :method: dup
-
# Duped objects have no id assigned and are treated as new records. Note
-
# that this is a "shallow" copy as it copies the object's attributes
-
# only, not its associations. The extent of a "deep" copy is application
-
# specific and is therefore left to the application to implement according
-
# to its need.
-
# The dup method does not preserve the timestamps (created|updated)_(at|on).
-
-
##
-
1
def initialize_dup(other) # :nodoc:
-
@attributes = @attributes.dup
-
@attributes.reset(self.class.primary_key)
-
-
_run_initialize_callbacks
-
-
@aggregation_cache = {}
-
@association_cache = {}
-
-
@new_record = true
-
@destroyed = false
-
-
super
-
end
-
-
# Populate +coder+ with attributes about this record that should be
-
# serialized. The structure of +coder+ defined in this method is
-
# guaranteed to match the structure of +coder+ passed to the +init_with+
-
# method.
-
#
-
# Example:
-
#
-
# class Post < ActiveRecord::Base
-
# end
-
# coder = {}
-
# Post.new.encode_with(coder)
-
# coder # => {"attributes" => {"id" => nil, ... }}
-
1
def encode_with(coder)
-
# FIXME: Remove this when we better serialize attributes
-
coder['raw_attributes'] = attributes_before_type_cast
-
coder['attributes'] = @attributes
-
coder['new_record'] = new_record?
-
coder['active_record_yaml_version'] = 0
-
end
-
-
# Returns true if +comparison_object+ is the same exact object, or +comparison_object+
-
# is of the same type and +self+ has an ID and it is equal to +comparison_object.id+.
-
#
-
# Note that new records are different from any other record by definition, unless the
-
# other record is the receiver itself. Besides, if you fetch existing records with
-
# +select+ and leave the ID out, you're on your own, this predicate will return false.
-
#
-
# Note also that destroying a record preserves its ID in the model instance, so deleted
-
# models are still comparable.
-
1
def ==(comparison_object)
-
super ||
-
comparison_object.instance_of?(self.class) &&
-
!id.nil? &&
-
14
comparison_object.id == id
-
end
-
1
alias :eql? :==
-
-
# Delegates to id in order to allow two records of the same type and id to work with something like:
-
# [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
-
1
def hash
-
if id
-
id.hash
-
else
-
super
-
end
-
end
-
-
# Clone and freeze the attributes hash such that associations are still
-
# accessible, even on destroyed records, but cloned models will not be
-
# frozen.
-
1
def freeze
-
1
@attributes = @attributes.clone.freeze
-
1
self
-
end
-
-
# Returns +true+ if the attributes hash has been frozen.
-
1
def frozen?
-
57
@attributes.frozen?
-
end
-
-
# Allows sort on objects
-
1
def <=>(other_object)
-
if other_object.is_a?(self.class)
-
self.to_key <=> other_object.to_key
-
else
-
super
-
end
-
end
-
-
# Returns +true+ if the record is read only. Records loaded through joins with piggy-back
-
# attributes will be marked as read only since they cannot be saved.
-
1
def readonly?
-
32
@readonly
-
end
-
-
# Marks this record as read only.
-
1
def readonly!
-
@readonly = true
-
end
-
-
1
def connection_handler
-
self.class.connection_handler
-
end
-
-
# Returns the contents of the record as a nicely formatted string.
-
1
def inspect
-
# We check defined?(@attributes) not to issue warnings if the object is
-
# allocated but not initialized.
-
inspection = if defined?(@attributes) && @attributes
-
self.class.column_names.collect { |name|
-
if has_attribute?(name)
-
"#{name}: #{attribute_for_inspect(name)}"
-
end
-
}.compact.join(", ")
-
else
-
"not initialized"
-
end
-
"#<#{self.class} #{inspection}>"
-
end
-
-
# Takes a PP and prettily prints this record to it, allowing you to get a nice result from `pp record`
-
# when pp is required.
-
1
def pretty_print(pp)
-
return super if custom_inspect_method_defined?
-
pp.object_address_group(self) do
-
if defined?(@attributes) && @attributes
-
column_names = self.class.column_names.select { |name| has_attribute?(name) || new_record? }
-
pp.seplist(column_names, proc { pp.text ',' }) do |column_name|
-
column_value = read_attribute(column_name)
-
pp.breakable ' '
-
pp.group(1) do
-
pp.text column_name
-
pp.text ':'
-
pp.breakable
-
pp.pp column_value
-
end
-
end
-
else
-
pp.breakable ' '
-
pp.text 'not initialized'
-
end
-
end
-
end
-
-
# Returns a hash of the given methods with their names as keys and returned values as values.
-
1
def slice(*methods)
-
Hash[methods.map! { |method| [method, public_send(method)] }].with_indifferent_access
-
end
-
-
1
private
-
-
1
def set_transaction_state(state) # :nodoc:
-
38
@transaction_state = state
-
end
-
-
1
def has_transactional_callbacks? # :nodoc:
-
50
!_rollback_callbacks.empty? || !_commit_callbacks.empty?
-
end
-
-
# Updates the attributes on this particular ActiveRecord object so that
-
# if it is associated with a transaction, then the state of the AR object
-
# will be updated to reflect the current state of the transaction
-
#
-
# The @transaction_state variable stores the states of the associated
-
# transaction. This relies on the fact that a transaction can only be in
-
# one rollback or commit (otherwise a list of states would be required)
-
# Each AR object inside of a transaction carries that transaction's
-
# TransactionState.
-
#
-
# This method checks to see if the ActiveRecord object's state reflects
-
# the TransactionState, and rolls back or commits the ActiveRecord object
-
# as appropriate.
-
#
-
# Since ActiveRecord objects can be inside multiple transactions, this
-
# method recursively goes through the parent of the TransactionState and
-
# checks if the ActiveRecord object reflects the state of the object.
-
1
def sync_with_transaction_state
-
187
update_attributes_from_transaction_state(@transaction_state, 0)
-
end
-
-
1
def update_attributes_from_transaction_state(transaction_state, depth)
-
187
@reflects_state = [false] if depth == 0
-
-
187
if transaction_state && transaction_state.finalized? && !has_transactional_callbacks?
-
12
unless @reflects_state[depth]
-
12
restore_transaction_record_state if transaction_state.rolledback?
-
12
clear_transaction_record_state
-
12
@reflects_state[depth] = true
-
end
-
-
12
if transaction_state.parent && !@reflects_state[depth+1]
-
update_attributes_from_transaction_state(transaction_state.parent, depth+1)
-
end
-
end
-
end
-
-
# Under Ruby 1.9, Array#flatten will call #to_ary (recursively) on each of the elements
-
# of the array, and then rescues from the possible NoMethodError. If those elements are
-
# ActiveRecord::Base's, then this triggers the various method_missing's that we have,
-
# which significantly impacts upon performance.
-
#
-
# So we can avoid the method_missing hit by explicitly defining #to_ary as nil here.
-
#
-
# See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary.html
-
1
def to_ary # :nodoc:
-
nil
-
end
-
-
1
def init_internals
-
70
@aggregation_cache = {}
-
70
@association_cache = {}
-
70
@readonly = false
-
70
@destroyed = false
-
70
@marked_for_destruction = false
-
70
@destroyed_by_association = nil
-
70
@new_record = true
-
70
@txn = nil
-
70
@_start_transaction_state = {}
-
70
@transaction_state = nil
-
end
-
-
1
def initialize_internals_callback
-
end
-
-
# This method is needed to make protected_attributes gem easier to hook.
-
# Remove it when we drop support to this gem.
-
1
def init_attributes(attributes, options)
-
13
assign_attributes(attributes)
-
end
-
-
1
def thaw
-
1
if frozen?
-
@attributes = @attributes.dup
-
end
-
end
-
-
1
def custom_inspect_method_defined?
-
self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner
-
end
-
end
-
end
-
1
module ActiveRecord
-
# = Active Record Counter Cache
-
1
module CounterCache
-
1
extend ActiveSupport::Concern
-
-
1
module ClassMethods
-
# Resets one or more counter caches to their correct value using an SQL
-
# count query. This is useful when adding new counter caches, or if the
-
# counter has been corrupted or modified directly by SQL.
-
#
-
# ==== Parameters
-
#
-
# * +id+ - The id of the object you wish to reset a counter on.
-
# * +counters+ - One or more association counters to reset. Association name or counter name can be given.
-
#
-
# ==== Examples
-
#
-
# # For Post with id #1 records reset the comments_count
-
# Post.reset_counters(1, :comments)
-
1
def reset_counters(id, *counters)
-
object = find(id)
-
counters.each do |counter_association|
-
has_many_association = _reflect_on_association(counter_association)
-
unless has_many_association
-
has_many = reflect_on_all_associations(:has_many)
-
has_many_association = has_many.find { |association| association.counter_cache_column && association.counter_cache_column.to_sym == counter_association.to_sym }
-
counter_association = has_many_association.plural_name if has_many_association
-
end
-
raise ArgumentError, "'#{self.name}' has no association called '#{counter_association}'" unless has_many_association
-
-
if has_many_association.is_a? ActiveRecord::Reflection::ThroughReflection
-
has_many_association = has_many_association.through_reflection
-
end
-
-
foreign_key = has_many_association.foreign_key.to_s
-
child_class = has_many_association.klass
-
reflection = child_class._reflections.values.find { |e| e.belongs_to? && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
-
counter_name = reflection.counter_cache_column
-
-
stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({
-
arel_table[counter_name] => object.send(counter_association).count(:all)
-
}, primary_key)
-
connection.update stmt
-
end
-
return true
-
end
-
-
# A generic "counter updater" implementation, intended primarily to be
-
# used by increment_counter and decrement_counter, but which may also
-
# be useful on its own. It simply does a direct SQL update for the record
-
# with the given ID, altering the given hash of counters by the amount
-
# given by the corresponding value:
-
#
-
# ==== Parameters
-
#
-
# * +id+ - The id of the object you wish to update a counter on or an Array of ids.
-
# * +counters+ - A Hash containing the names of the fields
-
# to update as keys and the amount to update the field by as values.
-
#
-
# ==== Examples
-
#
-
# # For the Post with id of 5, decrement the comment_count by 1, and
-
# # increment the action_count by 1
-
# Post.update_counters 5, comment_count: -1, action_count: 1
-
# # Executes the following SQL:
-
# # UPDATE posts
-
# # SET comment_count = COALESCE(comment_count, 0) - 1,
-
# # action_count = COALESCE(action_count, 0) + 1
-
# # WHERE id = 5
-
#
-
# # For the Posts with id of 10 and 15, increment the comment_count by 1
-
# Post.update_counters [10, 15], comment_count: 1
-
# # Executes the following SQL:
-
# # UPDATE posts
-
# # SET comment_count = COALESCE(comment_count, 0) + 1
-
# # WHERE id IN (10, 15)
-
1
def update_counters(id, counters)
-
updates = counters.map do |counter_name, value|
-
operator = value < 0 ? '-' : '+'
-
quoted_column = connection.quote_column_name(counter_name)
-
"#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
-
end
-
-
unscoped.where(primary_key => id).update_all updates.join(', ')
-
end
-
-
# Increment a numeric field by one, via a direct SQL update.
-
#
-
# This method is used primarily for maintaining counter_cache columns that are
-
# used to store aggregate values. For example, a DiscussionBoard may cache
-
# posts_count and comments_count to avoid running an SQL query to calculate the
-
# number of posts and comments there are, each time it is displayed.
-
#
-
# ==== Parameters
-
#
-
# * +counter_name+ - The name of the field that should be incremented.
-
# * +id+ - The id of the object that should be incremented or an Array of ids.
-
#
-
# ==== Examples
-
#
-
# # Increment the post_count column for the record with an id of 5
-
# DiscussionBoard.increment_counter(:post_count, 5)
-
1
def increment_counter(counter_name, id)
-
update_counters(id, counter_name => 1)
-
end
-
-
# Decrement a numeric field by one, via a direct SQL update.
-
#
-
# This works the same as increment_counter but reduces the column value by
-
# 1 instead of increasing it.
-
#
-
# ==== Parameters
-
#
-
# * +counter_name+ - The name of the field that should be decremented.
-
# * +id+ - The id of the object that should be decremented or an Array of ids.
-
#
-
# ==== Examples
-
#
-
# # Decrement the post_count column for the record with an id of 5
-
# DiscussionBoard.decrement_counter(:post_count, 5)
-
1
def decrement_counter(counter_name, id)
-
update_counters(id, counter_name => -1)
-
end
-
end
-
-
1
protected
-
-
1
def actually_destroyed?
-
@_actually_destroyed
-
end
-
-
1
def clear_destroy_state
-
@_actually_destroyed = nil
-
end
-
-
1
private
-
-
1
def _create_record(*)
-
28
id = super
-
-
28
each_counter_cached_associations do |association|
-
if send(association.reflection.name)
-
association.increment_counters
-
@_after_create_counter_called = true
-
end
-
end
-
-
28
id
-
end
-
-
1
def destroy_row
-
1
affected_rows = super
-
-
1
if affected_rows > 0
-
1
each_counter_cached_associations do |association|
-
foreign_key = association.reflection.foreign_key.to_sym
-
unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key
-
if send(association.reflection.name)
-
association.decrement_counters
-
end
-
end
-
end
-
end
-
-
1
affected_rows
-
end
-
-
1
def each_counter_cached_associations
-
29
_reflections.each do |name, reflection|
-
23
yield association(name.to_sym) if reflection.belongs_to? && reflection.counter_cache_column
-
end
-
end
-
-
end
-
end
-
1
module ActiveRecord
-
1
module DynamicMatchers #:nodoc:
-
# This code in this file seems to have a lot of indirection, but the indirection
-
# is there to provide extension points for the activerecord-deprecated_finders
-
# gem. When we stop supporting activerecord-deprecated_finders (from Rails 5),
-
# then we can remove the indirection.
-
-
1
def respond_to?(name, include_private = false)
-
455
if self == Base
-
2
super
-
else
-
453
match = Method.match(self, name)
-
453
match && match.valid? || super
-
end
-
end
-
-
1
private
-
-
1
def method_missing(name, *arguments, &block)
-
match = Method.match(self, name)
-
-
if match && match.valid?
-
match.define
-
send(name, *arguments, &block)
-
else
-
super
-
end
-
end
-
-
1
class Method
-
1
@matchers = []
-
-
1
class << self
-
1
attr_reader :matchers
-
-
1
def match(model, name)
-
1359
klass = matchers.find { |k| name =~ k.pattern }
-
453
klass.new(model, name) if klass
-
end
-
-
1
def pattern
-
906
@pattern ||= /\A#{prefix}_([_a-zA-Z]\w*)#{suffix}\Z/
-
end
-
-
1
def prefix
-
raise NotImplementedError
-
end
-
-
1
def suffix
-
1
''
-
end
-
end
-
-
1
attr_reader :model, :name, :attribute_names
-
-
1
def initialize(model, name)
-
@model = model
-
@name = name.to_s
-
@attribute_names = @name.match(self.class.pattern)[1].split('_and_')
-
@attribute_names.map! { |n| @model.attribute_aliases[n] || n }
-
end
-
-
1
def valid?
-
attribute_names.all? { |name| model.columns_hash[name] || model.reflect_on_aggregation(name.to_sym) }
-
end
-
-
1
def define
-
model.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def self.#{name}(#{signature})
-
#{body}
-
end
-
CODE
-
end
-
-
1
def body
-
raise NotImplementedError
-
end
-
end
-
-
1
module Finder
-
# Extended in activerecord-deprecated_finders
-
1
def body
-
result
-
end
-
-
# Extended in activerecord-deprecated_finders
-
1
def result
-
"#{finder}(#{attributes_hash})"
-
end
-
-
# The parameters in the signature may have reserved Ruby words, in order
-
# to prevent errors, we start each param name with `_`.
-
#
-
# Extended in activerecord-deprecated_finders
-
1
def signature
-
attribute_names.map { |name| "_#{name}" }.join(', ')
-
end
-
-
# Given that the parameters starts with `_`, the finder needs to use the
-
# same parameter name.
-
1
def attributes_hash
-
"{" + attribute_names.map { |name| ":#{name} => _#{name}" }.join(',') + "}"
-
end
-
-
1
def finder
-
raise NotImplementedError
-
end
-
end
-
-
1
class FindBy < Method
-
1
Method.matchers << self
-
1
include Finder
-
-
1
def self.prefix
-
1
"find_by"
-
end
-
-
1
def finder
-
"find_by"
-
end
-
end
-
-
1
class FindByBang < Method
-
1
Method.matchers << self
-
1
include Finder
-
-
1
def self.prefix
-
1
"find_by"
-
end
-
-
1
def self.suffix
-
1
"!"
-
end
-
-
1
def finder
-
"find_by!"
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/object/deep_dup'
-
-
1
module ActiveRecord
-
# Declare an enum attribute where the values map to integers in the database,
-
# but can be queried by name. Example:
-
#
-
# class Conversation < ActiveRecord::Base
-
# enum status: [ :active, :archived ]
-
# end
-
#
-
# # conversation.update! status: 0
-
# conversation.active!
-
# conversation.active? # => true
-
# conversation.status # => "active"
-
#
-
# # conversation.update! status: 1
-
# conversation.archived!
-
# conversation.archived? # => true
-
# conversation.status # => "archived"
-
#
-
# # conversation.status = 1
-
# conversation.status = "archived"
-
#
-
# conversation.status = nil
-
# conversation.status.nil? # => true
-
# conversation.status # => nil
-
#
-
# Scopes based on the allowed values of the enum field will be provided
-
# as well. With the above example:
-
#
-
# Conversation.active
-
# Conversation.archived
-
#
-
# You can set the default value from the database declaration, like:
-
#
-
# create_table :conversations do |t|
-
# t.column :status, :integer, default: 0
-
# end
-
#
-
# Good practice is to let the first declared status be the default.
-
#
-
# Finally, it's also possible to explicitly map the relation between attribute and
-
# database integer with a +Hash+:
-
#
-
# class Conversation < ActiveRecord::Base
-
# enum status: { active: 0, archived: 1 }
-
# end
-
#
-
# Note that when an +Array+ is used, the implicit mapping from the values to database
-
# integers is derived from the order the values appear in the array. In the example,
-
# <tt>:active</tt> is mapped to +0+ as it's the first element, and <tt>:archived</tt>
-
# is mapped to +1+. In general, the +i+-th element is mapped to <tt>i-1</tt> in the
-
# database.
-
#
-
# Therefore, once a value is added to the enum array, its position in the array must
-
# be maintained, and new values should only be added to the end of the array. To
-
# remove unused values, the explicit +Hash+ syntax should be used.
-
#
-
# In rare circumstances you might need to access the mapping directly.
-
# The mappings are exposed through a class method with the pluralized attribute
-
# name:
-
#
-
# Conversation.statuses # => { "active" => 0, "archived" => 1 }
-
#
-
# Use that class method when you need to know the ordinal value of an enum:
-
#
-
# Conversation.where("status <> ?", Conversation.statuses[:archived])
-
#
-
# Where conditions on an enum attribute must use the ordinal value of an enum.
-
1
module Enum
-
1
def self.extended(base) # :nodoc:
-
1
base.class_attribute(:defined_enums)
-
1
base.defined_enums = {}
-
end
-
-
1
def inherited(base) # :nodoc:
-
4
base.defined_enums = defined_enums.deep_dup
-
4
super
-
end
-
-
1
def enum(definitions)
-
klass = self
-
definitions.each do |name, values|
-
# statuses = { }
-
enum_values = ActiveSupport::HashWithIndifferentAccess.new
-
name = name.to_sym
-
-
# def self.statuses statuses end
-
detect_enum_conflict!(name, name.to_s.pluralize, true)
-
klass.singleton_class.send(:define_method, name.to_s.pluralize) { enum_values }
-
-
_enum_methods_module.module_eval do
-
# def status=(value) self[:status] = statuses[value] end
-
klass.send(:detect_enum_conflict!, name, "#{name}=")
-
define_method("#{name}=") { |value|
-
if enum_values.has_key?(value) || value.blank?
-
self[name] = enum_values[value]
-
elsif enum_values.has_value?(value)
-
# Assigning a value directly is not a end-user feature, hence it's not documented.
-
# This is used internally to make building objects from the generated scopes work
-
# as expected, i.e. +Conversation.archived.build.archived?+ should be true.
-
self[name] = value
-
else
-
raise ArgumentError, "'#{value}' is not a valid #{name}"
-
end
-
}
-
-
# def status() statuses.key self[:status] end
-
klass.send(:detect_enum_conflict!, name, name)
-
define_method(name) { enum_values.key self[name] }
-
-
# def status_before_type_cast() statuses.key self[:status] end
-
klass.send(:detect_enum_conflict!, name, "#{name}_before_type_cast")
-
define_method("#{name}_before_type_cast") { enum_values.key self[name] }
-
-
pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
-
pairs.each do |value, i|
-
enum_values[value] = i
-
-
# def active?() status == 0 end
-
klass.send(:detect_enum_conflict!, name, "#{value}?")
-
define_method("#{value}?") { self[name] == i }
-
-
# def active!() update! status: :active end
-
klass.send(:detect_enum_conflict!, name, "#{value}!")
-
define_method("#{value}!") { update! name => value }
-
-
# scope :active, -> { where status: 0 }
-
klass.send(:detect_enum_conflict!, name, value, true)
-
klass.scope value, -> { klass.where name => i }
-
end
-
end
-
defined_enums[name.to_s] = enum_values
-
end
-
end
-
-
1
private
-
1
def _enum_methods_module
-
@_enum_methods_module ||= begin
-
mod = Module.new do
-
private
-
def save_changed_attribute(attr_name, old)
-
if (mapping = self.class.defined_enums[attr_name.to_s])
-
value = _read_attribute(attr_name)
-
if attribute_changed?(attr_name)
-
if mapping[old] == value
-
clear_attribute_changes([attr_name])
-
end
-
else
-
if old != value
-
set_attribute_was(attr_name, mapping.key(old))
-
end
-
end
-
else
-
super
-
end
-
end
-
end
-
include mod
-
mod
-
end
-
end
-
-
1
ENUM_CONFLICT_MESSAGE = \
-
"You tried to define an enum named \"%{enum}\" on the model \"%{klass}\", but " \
-
"this will generate a %{type} method \"%{method}\", which is already defined " \
-
"by %{source}."
-
-
1
def detect_enum_conflict!(enum_name, method_name, klass_method = false)
-
if klass_method && dangerous_class_method?(method_name)
-
raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
-
enum: enum_name,
-
klass: self.name,
-
type: 'class',
-
method: method_name,
-
source: 'Active Record'
-
}
-
elsif !klass_method && dangerous_attribute_method?(method_name)
-
raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
-
enum: enum_name,
-
klass: self.name,
-
type: 'instance',
-
method: method_name,
-
source: 'Active Record'
-
}
-
elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module)
-
raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
-
enum: enum_name,
-
klass: self.name,
-
type: 'instance',
-
method: method_name,
-
source: 'another enum'
-
}
-
end
-
end
-
end
-
end
-
1
require 'active_support/lazy_load_hooks'
-
1
require 'active_record/explain_registry'
-
-
1
module ActiveRecord
-
1
module Explain
-
# Executes the block with the collect flag enabled. Queries are collected
-
# asynchronously by the subscriber and returned.
-
1
def collecting_queries_for_explain # :nodoc:
-
ExplainRegistry.collect = true
-
yield
-
ExplainRegistry.queries
-
ensure
-
ExplainRegistry.reset
-
end
-
-
# Makes the adapter execute EXPLAIN for the tuples of queries and bindings.
-
# Returns a formatted string ready to be logged.
-
1
def exec_explain(queries) # :nodoc:
-
str = queries.map do |sql, bind|
-
[].tap do |msg|
-
msg << "EXPLAIN for: #{sql}"
-
unless bind.empty?
-
bind_msg = bind.map {|col, val| [col.name, val]}.inspect
-
msg.last << " #{bind_msg}"
-
end
-
msg << connection.explain(sql, bind)
-
end.join("\n")
-
end.join("\n")
-
-
# Overriding inspect to be more human readable, especially in the console.
-
def str.inspect
-
self
-
end
-
-
str
-
end
-
end
-
end
-
1
require 'active_support/per_thread_registry'
-
-
1
module ActiveRecord
-
# This is a thread locals registry for EXPLAIN. For example
-
#
-
# ActiveRecord::ExplainRegistry.queries
-
#
-
# returns the collected queries local to the current thread.
-
#
-
# See the documentation of <tt>ActiveSupport::PerThreadRegistry</tt>
-
# for further details.
-
1
class ExplainRegistry # :nodoc:
-
1
extend ActiveSupport::PerThreadRegistry
-
-
1
attr_accessor :queries, :collect
-
-
1
def initialize
-
1
reset
-
end
-
-
1
def collect?
-
399
@collect
-
end
-
-
1
def reset
-
1
@collect = false
-
1
@queries = []
-
end
-
end
-
end
-
1
require 'active_support/notifications'
-
1
require 'active_record/explain_registry'
-
-
1
module ActiveRecord
-
1
class ExplainSubscriber # :nodoc:
-
1
def start(name, id, payload)
-
# unused
-
end
-
-
1
def finish(name, id, payload)
-
399
if ExplainRegistry.collect? && !ignore_payload?(payload)
-
ExplainRegistry.queries << payload.values_at(:sql, :binds)
-
end
-
end
-
-
# SCHEMA queries cannot be EXPLAINed, also we do not want to run EXPLAIN on
-
# our own EXPLAINs now matter how loopingly beautiful that would be.
-
#
-
# On the other hand, we want to monitor the performance of our real database
-
# queries, not the performance of the access to the query cache.
-
1
IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN CACHE)
-
1
EXPLAINED_SQLS = /\A\s*(with|select|update|delete|insert)\b/i
-
1
def ignore_payload?(payload)
-
payload[:exception] || IGNORED_PAYLOADS.include?(payload[:name]) || payload[:sql] !~ EXPLAINED_SQLS
-
end
-
-
1
ActiveSupport::Notifications.subscribe("sql.active_record", new)
-
end
-
end
-
1
require 'erb'
-
1
require 'yaml'
-
-
1
module ActiveRecord
-
1
class FixtureSet
-
1
class File # :nodoc:
-
1
include Enumerable
-
-
##
-
# Open a fixture file named +file+. When called with a block, the block
-
# is called with the filehandle and the filehandle is automatically closed
-
# when the block finishes.
-
1
def self.open(file)
-
x = new file
-
block_given? ? yield(x) : x
-
end
-
-
1
def initialize(file)
-
@file = file
-
@rows = nil
-
end
-
-
1
def each(&block)
-
rows.each(&block)
-
end
-
-
-
1
private
-
1
def rows
-
return @rows if @rows
-
-
begin
-
data = YAML.load(render(IO.read(@file)))
-
rescue ArgumentError, Psych::SyntaxError => error
-
raise Fixture::FormatError, "a YAML error occurred parsing #{@file}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{error.class}: #{error}", error.backtrace
-
end
-
@rows = data ? validate(data).to_a : []
-
end
-
-
1
def render(content)
-
context = ActiveRecord::FixtureSet::RenderContext.create_subclass.new
-
ERB.new(content).result(context.get_binding)
-
end
-
-
# Validate our unmarshalled data.
-
1
def validate(data)
-
unless Hash === data || YAML::Omap === data
-
raise Fixture::FormatError, 'fixture is not a hash'
-
end
-
-
raise Fixture::FormatError unless data.all? { |name, row| Hash === row }
-
data
-
end
-
end
-
end
-
end
-
1
require 'erb'
-
1
require 'yaml'
-
1
require 'zlib'
-
1
require 'active_support/dependencies'
-
1
require 'active_support/core_ext/digest/uuid'
-
1
require 'active_record/fixture_set/file'
-
1
require 'active_record/errors'
-
-
1
module ActiveRecord
-
1
class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc:
-
end
-
-
# \Fixtures are a way of organizing data that you want to test against; in short, sample data.
-
#
-
# They are stored in YAML files, one file per model, which are placed in the directory
-
# appointed by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically
-
# configured for Rails, so you can just put your files in <tt><your-rails-app>/test/fixtures/</tt>).
-
# The fixture file ends with the +.yml+ file extension, for example:
-
# <tt><your-rails-app>/test/fixtures/web_sites.yml</tt>).
-
#
-
# The format of a fixture file looks like this:
-
#
-
# rubyonrails:
-
# id: 1
-
# name: Ruby on Rails
-
# url: http://www.rubyonrails.org
-
#
-
# google:
-
# id: 2
-
# name: Google
-
# url: http://www.google.com
-
#
-
# This fixture file includes two fixtures. Each YAML fixture (ie. record) is given a name and
-
# is followed by an indented list of key/value pairs in the "key: value" format. Records are
-
# separated by a blank line for your viewing pleasure.
-
#
-
# Note: Fixtures are unordered. If you want ordered fixtures, use the omap YAML type.
-
# See http://yaml.org/type/omap.html
-
# for the specification. You will need ordered fixtures when you have foreign key constraints
-
# on keys in the same table. This is commonly needed for tree structures. Example:
-
#
-
# --- !omap
-
# - parent:
-
# id: 1
-
# parent_id: NULL
-
# title: Parent
-
# - child:
-
# id: 2
-
# parent_id: 1
-
# title: Child
-
#
-
# = Using Fixtures in Test Cases
-
#
-
# Since fixtures are a testing construct, we use them in our unit and functional tests. There
-
# are two ways to use the fixtures, but first let's take a look at a sample unit test:
-
#
-
# require 'test_helper'
-
#
-
# class WebSiteTest < ActiveSupport::TestCase
-
# test "web_site_count" do
-
# assert_equal 2, WebSite.count
-
# end
-
# end
-
#
-
# By default, +test_helper.rb+ will load all of your fixtures into your test
-
# database, so this test will succeed.
-
#
-
# The testing environment will automatically load the all fixtures into the database before each
-
# test. To ensure consistent data, the environment deletes the fixtures before running the load.
-
#
-
# In addition to being available in the database, the fixture's data may also be accessed by
-
# using a special dynamic method, which has the same name as the model, and accepts the
-
# name of the fixture to instantiate:
-
#
-
# test "find" do
-
# assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
-
# end
-
#
-
# Alternatively, you may enable auto-instantiation of the fixture data. For instance, take the
-
# following tests:
-
#
-
# test "find_alt_method_1" do
-
# assert_equal "Ruby on Rails", @web_sites['rubyonrails']['name']
-
# end
-
#
-
# test "find_alt_method_2" do
-
# assert_equal "Ruby on Rails", @rubyonrails.name
-
# end
-
#
-
# In order to use these methods to access fixtured data within your testcases, you must specify one of the
-
# following in your <tt>ActiveSupport::TestCase</tt>-derived class:
-
#
-
# - to fully enable instantiated fixtures (enable alternate methods #1 and #2 above)
-
# self.use_instantiated_fixtures = true
-
#
-
# - create only the hash for the fixtures, do not 'find' each instance (enable alternate method #1 only)
-
# self.use_instantiated_fixtures = :no_instances
-
#
-
# Using either of these alternate methods incurs a performance hit, as the fixtured data must be fully
-
# traversed in the database to create the fixture hash and/or instance variables. This is expensive for
-
# large sets of fixtured data.
-
#
-
# = Dynamic fixtures with ERB
-
#
-
# Some times you don't care about the content of the fixtures as much as you care about the volume.
-
# In these cases, you can mix ERB in with your YAML fixtures to create a bunch of fixtures for load
-
# testing, like:
-
#
-
# <% 1.upto(1000) do |i| %>
-
# fix_<%= i %>:
-
# id: <%= i %>
-
# name: guy_<%= 1 %>
-
# <% end %>
-
#
-
# This will create 1000 very simple fixtures.
-
#
-
# Using ERB, you can also inject dynamic values into your fixtures with inserts like
-
# <tt><%= Date.today.strftime("%Y-%m-%d") %></tt>.
-
# This is however a feature to be used with some caution. The point of fixtures are that they're
-
# stable units of predictable sample data. If you feel that you need to inject dynamic values, then
-
# perhaps you should reexamine whether your application is properly testable. Hence, dynamic values
-
# in fixtures are to be considered a code smell.
-
#
-
# Helper methods defined in a fixture will not be available in other fixtures, to prevent against
-
# unwanted inter-test dependencies. Methods used by multiple fixtures should be defined in a module
-
# that is included in <tt>ActiveRecord::FixtureSet.context_class</tt>.
-
#
-
# - define a helper method in `test_helper.rb`
-
# module FixtureFileHelpers
-
# def file_sha(path)
-
# Digest::SHA2.hexdigest(File.read(Rails.root.join('test/fixtures', path)))
-
# end
-
# end
-
# ActiveRecord::FixtureSet.context_class.send :include, FixtureFileHelpers
-
#
-
# - use the helper method in a fixture
-
# photo:
-
# name: kitten.png
-
# sha: <%= file_sha 'files/kitten.png' %>
-
#
-
# = Transactional Fixtures
-
#
-
# Test cases can use begin+rollback to isolate their changes to the database instead of having to
-
# delete+insert for every test case.
-
#
-
# class FooTest < ActiveSupport::TestCase
-
# self.use_transactional_fixtures = true
-
#
-
# test "godzilla" do
-
# assert !Foo.all.empty?
-
# Foo.destroy_all
-
# assert Foo.all.empty?
-
# end
-
#
-
# test "godzilla aftermath" do
-
# assert !Foo.all.empty?
-
# end
-
# end
-
#
-
# If you preload your test database with all fixture data (probably in the rake task) and use
-
# transactional fixtures, then you may omit all fixtures declarations in your test cases since
-
# all the data's already there and every case rolls back its changes.
-
#
-
# In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to
-
# true. This will provide access to fixture data for every table that has been loaded through
-
# fixtures (depending on the value of +use_instantiated_fixtures+).
-
#
-
# When *not* to use transactional fixtures:
-
#
-
# 1. You're testing whether a transaction works correctly. Nested transactions don't commit until
-
# all parent transactions commit, particularly, the fixtures transaction which is begun in setup
-
# and rolled back in teardown. Thus, you won't be able to verify
-
# the results of your transaction until Active Record supports nested transactions or savepoints (in progress).
-
# 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM.
-
# Use InnoDB, MaxDB, or NDB instead.
-
#
-
# = Advanced Fixtures
-
#
-
# Fixtures that don't specify an ID get some extra features:
-
#
-
# * Stable, autogenerated IDs
-
# * Label references for associations (belongs_to, has_one, has_many)
-
# * HABTM associations as inline lists
-
#
-
# There are some more advanced features available even if the id is specified:
-
#
-
# * Autofilled timestamp columns
-
# * Fixture label interpolation
-
# * Support for YAML defaults
-
#
-
# == Stable, Autogenerated IDs
-
#
-
# Here, have a monkey fixture:
-
#
-
# george:
-
# id: 1
-
# name: George the Monkey
-
#
-
# reginald:
-
# id: 2
-
# name: Reginald the Pirate
-
#
-
# Each of these fixtures has two unique identifiers: one for the database
-
# and one for the humans. Why don't we generate the primary key instead?
-
# Hashing each fixture's label yields a consistent ID:
-
#
-
# george: # generated id: 503576764
-
# name: George the Monkey
-
#
-
# reginald: # generated id: 324201669
-
# name: Reginald the Pirate
-
#
-
# Active Record looks at the fixture's model class, discovers the correct
-
# primary key, and generates it right before inserting the fixture
-
# into the database.
-
#
-
# The generated ID for a given label is constant, so we can discover
-
# any fixture's ID without loading anything, as long as we know the label.
-
#
-
# == Label references for associations (belongs_to, has_one, has_many)
-
#
-
# Specifying foreign keys in fixtures can be very fragile, not to
-
# mention difficult to read. Since Active Record can figure out the ID of
-
# any fixture from its label, you can specify FK's by label instead of ID.
-
#
-
# === belongs_to
-
#
-
# Let's break out some more monkeys and pirates.
-
#
-
# ### in pirates.yml
-
#
-
# reginald:
-
# id: 1
-
# name: Reginald the Pirate
-
# monkey_id: 1
-
#
-
# ### in monkeys.yml
-
#
-
# george:
-
# id: 1
-
# name: George the Monkey
-
# pirate_id: 1
-
#
-
# Add a few more monkeys and pirates and break this into multiple files,
-
# and it gets pretty hard to keep track of what's going on. Let's
-
# use labels instead of IDs:
-
#
-
# ### in pirates.yml
-
#
-
# reginald:
-
# name: Reginald the Pirate
-
# monkey: george
-
#
-
# ### in monkeys.yml
-
#
-
# george:
-
# name: George the Monkey
-
# pirate: reginald
-
#
-
# Pow! All is made clear. Active Record reflects on the fixture's model class,
-
# finds all the +belongs_to+ associations, and allows you to specify
-
# a target *label* for the *association* (monkey: george) rather than
-
# a target *id* for the *FK* (<tt>monkey_id: 1</tt>).
-
#
-
# ==== Polymorphic belongs_to
-
#
-
# Supporting polymorphic relationships is a little bit more complicated, since
-
# Active Record needs to know what type your association is pointing at. Something
-
# like this should look familiar:
-
#
-
# ### in fruit.rb
-
#
-
# belongs_to :eater, polymorphic: true
-
#
-
# ### in fruits.yml
-
#
-
# apple:
-
# id: 1
-
# name: apple
-
# eater_id: 1
-
# eater_type: Monkey
-
#
-
# Can we do better? You bet!
-
#
-
# apple:
-
# eater: george (Monkey)
-
#
-
# Just provide the polymorphic target type and Active Record will take care of the rest.
-
#
-
# === has_and_belongs_to_many
-
#
-
# Time to give our monkey some fruit.
-
#
-
# ### in monkeys.yml
-
#
-
# george:
-
# id: 1
-
# name: George the Monkey
-
#
-
# ### in fruits.yml
-
#
-
# apple:
-
# id: 1
-
# name: apple
-
#
-
# orange:
-
# id: 2
-
# name: orange
-
#
-
# grape:
-
# id: 3
-
# name: grape
-
#
-
# ### in fruits_monkeys.yml
-
#
-
# apple_george:
-
# fruit_id: 1
-
# monkey_id: 1
-
#
-
# orange_george:
-
# fruit_id: 2
-
# monkey_id: 1
-
#
-
# grape_george:
-
# fruit_id: 3
-
# monkey_id: 1
-
#
-
# Let's make the HABTM fixture go away.
-
#
-
# ### in monkeys.yml
-
#
-
# george:
-
# id: 1
-
# name: George the Monkey
-
# fruits: apple, orange, grape
-
#
-
# ### in fruits.yml
-
#
-
# apple:
-
# name: apple
-
#
-
# orange:
-
# name: orange
-
#
-
# grape:
-
# name: grape
-
#
-
# Zap! No more fruits_monkeys.yml file. We've specified the list of fruits
-
# on George's fixture, but we could've just as easily specified a list
-
# of monkeys on each fruit. As with +belongs_to+, Active Record reflects on
-
# the fixture's model class and discovers the +has_and_belongs_to_many+
-
# associations.
-
#
-
# == Autofilled Timestamp Columns
-
#
-
# If your table/model specifies any of Active Record's
-
# standard timestamp columns (+created_at+, +created_on+, +updated_at+, +updated_on+),
-
# they will automatically be set to <tt>Time.now</tt>.
-
#
-
# If you've set specific values, they'll be left alone.
-
#
-
# == Fixture label interpolation
-
#
-
# The label of the current fixture is always available as a column value:
-
#
-
# geeksomnia:
-
# name: Geeksomnia's Account
-
# subdomain: $LABEL
-
# email: $LABEL@email.com
-
#
-
# Also, sometimes (like when porting older join table fixtures) you'll need
-
# to be able to get a hold of the identifier for a given label. ERB
-
# to the rescue:
-
#
-
# george_reginald:
-
# monkey_id: <%= ActiveRecord::FixtureSet.identify(:reginald) %>
-
# pirate_id: <%= ActiveRecord::FixtureSet.identify(:george) %>
-
#
-
# == Support for YAML defaults
-
#
-
# You can set and reuse defaults in your fixtures YAML file.
-
# This is the same technique used in the +database.yml+ file to specify
-
# defaults:
-
#
-
# DEFAULTS: &DEFAULTS
-
# created_on: <%= 3.weeks.ago.to_s(:db) %>
-
#
-
# first:
-
# name: Smurf
-
# <<: *DEFAULTS
-
#
-
# second:
-
# name: Fraggle
-
# <<: *DEFAULTS
-
#
-
# Any fixture labeled "DEFAULTS" is safely ignored.
-
1
class FixtureSet
-
#--
-
# An instance of FixtureSet is normally stored in a single YAML file and
-
# possibly in a folder with the same name.
-
#++
-
-
1
MAX_ID = 2 ** 30 - 1
-
-
2
@@all_cached_fixtures = Hash.new { |h,k| h[k] = {} }
-
-
1
def self.default_fixture_model_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc:
-
config.pluralize_table_names ?
-
fixture_set_name.singularize.camelize :
-
fixture_set_name.camelize
-
end
-
-
1
def self.default_fixture_table_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc:
-
"#{ config.table_name_prefix }"\
-
"#{ fixture_set_name.tr('/', '_') }"\
-
"#{ config.table_name_suffix }".to_sym
-
end
-
-
1
def self.reset_cache
-
@@all_cached_fixtures.clear
-
end
-
-
1
def self.cache_for_connection(connection)
-
16
@@all_cached_fixtures[connection]
-
end
-
-
1
def self.fixture_is_cached?(connection, table_name)
-
cache_for_connection(connection)[table_name]
-
end
-
-
1
def self.cached_fixtures(connection, keys_to_fetch = nil)
-
16
if keys_to_fetch
-
16
cache_for_connection(connection).values_at(*keys_to_fetch)
-
else
-
cache_for_connection(connection).values
-
end
-
end
-
-
1
def self.cache_fixtures(connection, fixtures_map)
-
cache_for_connection(connection).update(fixtures_map)
-
end
-
-
1
def self.instantiate_fixtures(object, fixture_set, load_instances = true)
-
if load_instances
-
fixture_set.each do |fixture_name, fixture|
-
begin
-
object.instance_variable_set "@#{fixture_name}", fixture.find
-
rescue FixtureClassNotFound
-
nil
-
end
-
end
-
end
-
end
-
-
1
def self.instantiate_all_loaded_fixtures(object, load_instances = true)
-
all_loaded_fixtures.each_value do |fixture_set|
-
instantiate_fixtures(object, fixture_set, load_instances)
-
end
-
end
-
-
1
cattr_accessor :all_loaded_fixtures
-
1
self.all_loaded_fixtures = {}
-
-
1
class ClassCache
-
1
def initialize(class_names, config)
-
16
@class_names = class_names.stringify_keys
-
16
@config = config
-
-
# Remove string values that aren't constants or subclasses of AR
-
16
@class_names.delete_if { |klass_name, klass| !insert_class(@class_names, klass_name, klass) }
-
end
-
-
1
def [](fs_name)
-
@class_names.fetch(fs_name) {
-
klass = default_fixture_model(fs_name, @config).safe_constantize
-
insert_class(@class_names, fs_name, klass)
-
}
-
end
-
-
1
private
-
-
1
def insert_class(class_names, name, klass)
-
# We only want to deal with AR objects.
-
if klass && klass < ActiveRecord::Base
-
class_names[name] = klass
-
else
-
class_names[name] = nil
-
end
-
end
-
-
1
def default_fixture_model(fs_name, config)
-
ActiveRecord::FixtureSet.default_fixture_model_name(fs_name, config)
-
end
-
end
-
-
1
def self.create_fixtures(fixtures_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base)
-
16
fixture_set_names = Array(fixture_set_names).map(&:to_s)
-
16
class_names = ClassCache.new class_names, config
-
-
# FIXME: Apparently JK uses this.
-
16
connection = block_given? ? yield : ActiveRecord::Base.connection
-
-
16
files_to_read = fixture_set_names.reject { |fs_name|
-
fixture_is_cached?(connection, fs_name)
-
}
-
-
16
unless files_to_read.empty?
-
connection.disable_referential_integrity do
-
fixtures_map = {}
-
-
fixture_sets = files_to_read.map do |fs_name|
-
klass = class_names[fs_name]
-
conn = klass ? klass.connection : connection
-
fixtures_map[fs_name] = new( # ActiveRecord::FixtureSet.new
-
conn,
-
fs_name,
-
klass,
-
::File.join(fixtures_directory, fs_name))
-
end
-
-
update_all_loaded_fixtures fixtures_map
-
-
connection.transaction(:requires_new => true) do
-
fixture_sets.each do |fs|
-
conn = fs.model_class.respond_to?(:connection) ? fs.model_class.connection : connection
-
table_rows = fs.table_rows
-
-
table_rows.each_key do |table|
-
conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete'
-
end
-
-
table_rows.each do |fixture_set_name, rows|
-
rows.each do |row|
-
conn.insert_fixture(row, fixture_set_name)
-
end
-
end
-
-
# Cap primary key sequences to max(pk).
-
if conn.respond_to?(:reset_pk_sequence!)
-
conn.reset_pk_sequence!(fs.table_name)
-
end
-
end
-
end
-
-
cache_fixtures(connection, fixtures_map)
-
end
-
end
-
16
cached_fixtures(connection, fixture_set_names)
-
end
-
-
# Returns a consistent, platform-independent identifier for +label+.
-
# Integer identifiers are values less than 2^30. UUIDs are RFC 4122 version 5 SHA-1 hashes.
-
1
def self.identify(label, column_type = :integer)
-
if column_type == :uuid
-
Digest::UUID.uuid_v5(Digest::UUID::OID_NAMESPACE, label.to_s)
-
else
-
Zlib.crc32(label.to_s) % MAX_ID
-
end
-
end
-
-
# Superclass for the evaluation contexts used by ERB fixtures.
-
1
def self.context_class
-
@context_class ||= Class.new
-
end
-
-
1
def self.update_all_loaded_fixtures(fixtures_map) # :nodoc:
-
all_loaded_fixtures.update(fixtures_map)
-
end
-
-
1
attr_reader :table_name, :name, :fixtures, :model_class, :config
-
-
1
def initialize(connection, name, class_name, path, config = ActiveRecord::Base)
-
@name = name
-
@path = path
-
@config = config
-
@model_class = nil
-
-
if class_name.is_a?(Class) # TODO: Should be an AR::Base type class, or any?
-
@model_class = class_name
-
else
-
@model_class = class_name.safe_constantize if class_name
-
end
-
-
@connection = connection
-
-
@table_name = ( model_class.respond_to?(:table_name) ?
-
model_class.table_name :
-
self.class.default_fixture_table_name(name, config) )
-
-
@fixtures = read_fixture_files path, @model_class
-
end
-
-
1
def [](x)
-
fixtures[x]
-
end
-
-
1
def []=(k,v)
-
fixtures[k] = v
-
end
-
-
1
def each(&block)
-
fixtures.each(&block)
-
end
-
-
1
def size
-
fixtures.size
-
end
-
-
# Returns a hash of rows to be inserted. The key is the table, the value is
-
# a list of rows to insert to that table.
-
1
def table_rows
-
now = config.default_timezone == :utc ? Time.now.utc : Time.now
-
now = now.to_s(:db)
-
-
# allow a standard key to be used for doing defaults in YAML
-
fixtures.delete('DEFAULTS')
-
-
# track any join tables we need to insert later
-
rows = Hash.new { |h,table| h[table] = [] }
-
-
rows[table_name] = fixtures.map do |label, fixture|
-
row = fixture.to_hash
-
-
if model_class
-
# fill in timestamp columns if they aren't specified and the model is set to record_timestamps
-
if model_class.record_timestamps
-
timestamp_column_names.each do |c_name|
-
row[c_name] = now unless row.key?(c_name)
-
end
-
end
-
-
# interpolate the fixture label
-
row.each do |key, value|
-
row[key] = value.gsub("$LABEL", label.to_s) if value.is_a?(String)
-
end
-
-
# generate a primary key if necessary
-
if has_primary_key_column? && !row.include?(primary_key_name)
-
row[primary_key_name] = ActiveRecord::FixtureSet.identify(label, primary_key_type)
-
end
-
-
# If STI is used, find the correct subclass for association reflection
-
reflection_class =
-
if row.include?(inheritance_column_name)
-
row[inheritance_column_name].constantize rescue model_class
-
else
-
model_class
-
end
-
-
reflection_class._reflections.each_value do |association|
-
case association.macro
-
when :belongs_to
-
# Do not replace association name with association foreign key if they are named the same
-
fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
-
-
if association.name.to_s != fk_name && value = row.delete(association.name.to_s)
-
if association.polymorphic? && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
-
# support polymorphic belongs_to as "label (Type)"
-
row[association.foreign_type] = $1
-
end
-
-
fk_type = reflection_class.columns_hash[fk_name].type
-
row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type)
-
end
-
when :has_many
-
if association.options[:through]
-
add_join_records(rows, row, HasManyThroughProxy.new(association))
-
end
-
end
-
end
-
end
-
-
row
-
end
-
rows
-
end
-
-
1
class ReflectionProxy # :nodoc:
-
1
def initialize(association)
-
@association = association
-
end
-
-
1
def join_table
-
@association.join_table
-
end
-
-
1
def name
-
@association.name
-
end
-
-
1
def primary_key_type
-
@association.klass.column_types[@association.klass.primary_key].type
-
end
-
end
-
-
1
class HasManyThroughProxy < ReflectionProxy # :nodoc:
-
1
def rhs_key
-
@association.foreign_key
-
end
-
-
1
def lhs_key
-
@association.through_reflection.foreign_key
-
end
-
-
1
def join_table
-
@association.through_reflection.table_name
-
end
-
end
-
-
1
private
-
1
def primary_key_name
-
@primary_key_name ||= model_class && model_class.primary_key
-
end
-
-
1
def primary_key_type
-
@primary_key_type ||= model_class && model_class.column_types[model_class.primary_key].type
-
end
-
-
1
def add_join_records(rows, row, association)
-
# This is the case when the join table has no fixtures file
-
if (targets = row.delete(association.name.to_s))
-
table_name = association.join_table
-
column_type = association.primary_key_type
-
lhs_key = association.lhs_key
-
rhs_key = association.rhs_key
-
-
targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
-
rows[table_name].concat targets.map { |target|
-
{ lhs_key => row[primary_key_name],
-
rhs_key => ActiveRecord::FixtureSet.identify(target, column_type) }
-
}
-
end
-
end
-
-
1
def has_primary_key_column?
-
@has_primary_key_column ||= primary_key_name &&
-
model_class.columns.any? { |c| c.name == primary_key_name }
-
end
-
-
1
def timestamp_column_names
-
@timestamp_column_names ||=
-
%w(created_at created_on updated_at updated_on) & column_names
-
end
-
-
1
def inheritance_column_name
-
@inheritance_column_name ||= model_class && model_class.inheritance_column
-
end
-
-
1
def column_names
-
@column_names ||= @connection.columns(@table_name).collect { |c| c.name }
-
end
-
-
1
def read_fixture_files(path, model_class)
-
yaml_files = Dir["#{path}/{**,*}/*.yml"].select { |f|
-
::File.file?(f)
-
} + [yaml_file_path(path)]
-
-
yaml_files.each_with_object({}) do |file, fixtures|
-
FixtureSet::File.open(file) do |fh|
-
fh.each do |fixture_name, row|
-
fixtures[fixture_name] = ActiveRecord::Fixture.new(row, model_class)
-
end
-
end
-
end
-
end
-
-
1
def yaml_file_path(path)
-
"#{path}.yml"
-
end
-
-
end
-
-
#--
-
# Deprecate 'Fixtures' in favor of 'FixtureSet'.
-
#++
-
# :nodoc:
-
1
Fixtures = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('ActiveRecord::Fixtures', 'ActiveRecord::FixtureSet')
-
-
1
class Fixture #:nodoc:
-
1
include Enumerable
-
-
1
class FixtureError < StandardError #:nodoc:
-
end
-
-
1
class FormatError < FixtureError #:nodoc:
-
end
-
-
1
attr_reader :model_class, :fixture
-
-
1
def initialize(fixture, model_class)
-
@fixture = fixture
-
@model_class = model_class
-
end
-
-
1
def class_name
-
model_class.name if model_class
-
end
-
-
1
def each
-
fixture.each { |item| yield item }
-
end
-
-
1
def [](key)
-
fixture[key]
-
end
-
-
1
alias :to_hash :fixture
-
-
1
def find
-
if model_class
-
model_class.unscoped do
-
model_class.find(fixture[model_class.primary_key])
-
end
-
else
-
raise FixtureClassNotFound, "No class attached to find."
-
end
-
end
-
end
-
end
-
-
1
module ActiveRecord
-
1
module TestFixtures
-
1
extend ActiveSupport::Concern
-
-
1
def before_setup
-
28
setup_fixtures
-
28
super
-
end
-
-
1
def after_teardown
-
28
super
-
28
teardown_fixtures
-
end
-
-
1
included do
-
7
class_attribute :fixture_path, :instance_writer => false
-
7
class_attribute :fixture_table_names
-
7
class_attribute :fixture_class_names
-
7
class_attribute :use_transactional_fixtures
-
7
class_attribute :use_instantiated_fixtures # true, false, or :no_instances
-
7
class_attribute :pre_loaded_fixtures
-
7
class_attribute :config
-
-
7
self.fixture_table_names = []
-
7
self.use_transactional_fixtures = true
-
7
self.use_instantiated_fixtures = false
-
7
self.pre_loaded_fixtures = false
-
7
self.config = ActiveRecord::Base
-
-
7
self.fixture_class_names = Hash.new do |h, fixture_set_name|
-
h[fixture_set_name] = ActiveRecord::FixtureSet.default_fixture_model_name(fixture_set_name, self.config)
-
end
-
end
-
-
1
module ClassMethods
-
# Sets the model class for a fixture when the class name cannot be inferred from the fixture name.
-
#
-
# Examples:
-
#
-
# set_fixture_class some_fixture: SomeModel,
-
# 'namespaced/fixture' => Another::Model
-
#
-
# The keys must be the fixture names, that coincide with the short paths to the fixture files.
-
1
def set_fixture_class(class_names = {})
-
self.fixture_class_names = self.fixture_class_names.merge(class_names.stringify_keys)
-
end
-
-
1
def fixtures(*fixture_set_names)
-
if fixture_set_names.first == :all
-
fixture_set_names = Dir["#{fixture_path}/{**,*}/*.{yml}"]
-
fixture_set_names.map! { |f| f[(fixture_path.to_s.size + 1)..-5] }
-
else
-
fixture_set_names = fixture_set_names.flatten.map { |n| n.to_s }
-
end
-
-
self.fixture_table_names |= fixture_set_names
-
setup_fixture_accessors(fixture_set_names)
-
end
-
-
1
def setup_fixture_accessors(fixture_set_names = nil)
-
fixture_set_names = Array(fixture_set_names || fixture_table_names)
-
methods = Module.new do
-
fixture_set_names.each do |fs_name|
-
fs_name = fs_name.to_s
-
accessor_name = fs_name.tr('/', '_').to_sym
-
-
define_method(accessor_name) do |*fixture_names|
-
force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
-
-
@fixture_cache[fs_name] ||= {}
-
-
instances = fixture_names.map do |f_name|
-
f_name = f_name.to_s
-
@fixture_cache[fs_name].delete(f_name) if force_reload
-
-
if @loaded_fixtures[fs_name][f_name]
-
@fixture_cache[fs_name][f_name] ||= @loaded_fixtures[fs_name][f_name].find
-
else
-
raise StandardError, "No fixture named '#{f_name}' found for fixture set '#{fs_name}'"
-
end
-
end
-
-
instances.size == 1 ? instances.first : instances
-
end
-
private accessor_name
-
end
-
end
-
include methods
-
end
-
-
1
def uses_transaction(*methods)
-
@uses_transaction = [] unless defined?(@uses_transaction)
-
@uses_transaction.concat methods.map { |m| m.to_s }
-
end
-
-
1
def uses_transaction?(method)
-
56
@uses_transaction = [] unless defined?(@uses_transaction)
-
56
@uses_transaction.include?(method.to_s)
-
end
-
end
-
-
1
def run_in_transaction?
-
use_transactional_fixtures &&
-
56
!self.class.uses_transaction?(method_name)
-
end
-
-
1
def setup_fixtures(config = ActiveRecord::Base)
-
28
if pre_loaded_fixtures && !use_transactional_fixtures
-
raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
-
end
-
-
28
@fixture_cache = {}
-
28
@fixture_connections = []
-
28
@@already_loaded_fixtures ||= {}
-
-
# Load fixtures once and begin transaction.
-
28
if run_in_transaction?
-
28
if @@already_loaded_fixtures[self.class]
-
12
@loaded_fixtures = @@already_loaded_fixtures[self.class]
-
else
-
16
@loaded_fixtures = load_fixtures(config)
-
16
@@already_loaded_fixtures[self.class] = @loaded_fixtures
-
end
-
28
@fixture_connections = enlist_fixture_connections
-
28
@fixture_connections.each do |connection|
-
28
connection.begin_transaction joinable: false
-
end
-
# Load fixtures for every test.
-
else
-
ActiveRecord::FixtureSet.reset_cache
-
@@already_loaded_fixtures[self.class] = nil
-
@loaded_fixtures = load_fixtures(config)
-
end
-
-
# Instantiate fixtures for every test if requested.
-
28
instantiate_fixtures if use_instantiated_fixtures
-
end
-
-
1
def teardown_fixtures
-
# Rollback changes if a transaction is active.
-
28
if run_in_transaction?
-
28
@fixture_connections.each do |connection|
-
28
connection.rollback_transaction if connection.transaction_open?
-
end
-
28
@fixture_connections.clear
-
else
-
ActiveRecord::FixtureSet.reset_cache
-
end
-
-
28
ActiveRecord::Base.clear_active_connections!
-
end
-
-
1
def enlist_fixture_connections
-
28
ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection)
-
end
-
-
1
private
-
1
def load_fixtures(config)
-
16
fixtures = ActiveRecord::FixtureSet.create_fixtures(fixture_path, fixture_table_names, fixture_class_names, config)
-
16
Hash[fixtures.map { |f| [f.name, f] }]
-
end
-
-
1
def instantiate_fixtures
-
if pre_loaded_fixtures
-
raise RuntimeError, 'Load fixtures before instantiating them.' if ActiveRecord::FixtureSet.all_loaded_fixtures.empty?
-
ActiveRecord::FixtureSet.instantiate_all_loaded_fixtures(self, load_instances?)
-
else
-
raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
-
@loaded_fixtures.each_value do |fixture_set|
-
ActiveRecord::FixtureSet.instantiate_fixtures(self, fixture_set, load_instances?)
-
end
-
end
-
end
-
-
1
def load_instances?
-
use_instantiated_fixtures != :no_instances
-
end
-
end
-
end
-
-
1
class ActiveRecord::FixtureSet::RenderContext # :nodoc:
-
1
def self.create_subclass
-
Class.new ActiveRecord::FixtureSet.context_class do
-
def get_binding
-
binding()
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/hash/indifferent_access'
-
-
1
module ActiveRecord
-
# == Single table inheritance
-
#
-
# Active Record allows inheritance by storing the name of the class in a column that by
-
# default is named "type" (can be changed by overwriting <tt>Base.inheritance_column</tt>).
-
# This means that an inheritance looking like this:
-
#
-
# class Company < ActiveRecord::Base; end
-
# class Firm < Company; end
-
# class Client < Company; end
-
# class PriorityClient < Client; end
-
#
-
# When you do <tt>Firm.create(name: "37signals")</tt>, this record will be saved in
-
# the companies table with type = "Firm". You can then fetch this row again using
-
# <tt>Company.where(name: '37signals').first</tt> and it will return a Firm object.
-
#
-
# Be aware that because the type column is an attribute on the record every new
-
# subclass will instantly be marked as dirty and the type column will be included
-
# in the list of changed attributes on the record. This is different from non
-
# STI classes:
-
#
-
# Company.new.changed? # => false
-
# Firm.new.changed? # => true
-
# Firm.new.changes # => {"type"=>["","Firm"]}
-
#
-
# If you don't have a type column defined in your table, single-table inheritance won't
-
# be triggered. In that case, it'll work just like normal subclasses with no special magic
-
# for differentiating between them or reloading the right type with find.
-
#
-
# Note, all the attributes for all the cases are kept in the same table. Read more:
-
# http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
-
#
-
1
module Inheritance
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
# Determines whether to store the full constant name including namespace when using STI.
-
1
class_attribute :store_full_sti_class, instance_writer: false
-
1
self.store_full_sti_class = true
-
end
-
-
1
module ClassMethods
-
# Determines if one of the attributes passed in is the inheritance column,
-
# and if the inheritance column is attr accessible, it initializes an
-
# instance of the given subclass instead of the base class.
-
1
def new(*args, &block)
-
43
if abstract_class? || self == Base
-
raise NotImplementedError, "#{self} is an abstract class and cannot be instantiated."
-
end
-
-
43
attrs = args.first
-
43
if subclass_from_attributes?(attrs)
-
subclass = subclass_from_attributes(attrs)
-
end
-
-
43
if subclass
-
subclass.new(*args, &block)
-
else
-
43
super
-
end
-
end
-
-
# Returns +true+ if this does not need STI type condition. Returns
-
# +false+ if STI type condition needs to be applied.
-
1
def descends_from_active_record?
-
4
if self == Base
-
false
-
4
elsif superclass.abstract_class?
-
superclass.descends_from_active_record?
-
else
-
4
superclass == Base || !columns_hash.include?(inheritance_column)
-
end
-
end
-
-
1
def finder_needs_type_condition? #:nodoc:
-
# This is like this because benchmarking justifies the strange :false stuff
-
228
:true == (@finder_needs_type_condition ||= descends_from_active_record? ? :false : :true)
-
end
-
-
1
def symbolized_base_class
-
ActiveSupport::Deprecation.warn('`ActiveRecord::Base.symbolized_base_class` is deprecated and will be removed without replacement.')
-
@symbolized_base_class ||= base_class.to_s.to_sym
-
end
-
-
1
def symbolized_sti_name
-
ActiveSupport::Deprecation.warn('`ActiveRecord::Base.symbolized_sti_name` is deprecated and will be removed without replacement.')
-
@symbolized_sti_name ||= sti_name.present? ? sti_name.to_sym : symbolized_base_class
-
end
-
-
# Returns the class descending directly from ActiveRecord::Base, or
-
# an abstract class, if any, in the inheritance hierarchy.
-
#
-
# If A extends AR::Base, A.base_class will return A. If B descends from A
-
# through some arbitrarily deep hierarchy, B.base_class will return A.
-
#
-
# If B < A and C < B and if A is an abstract_class then both B.base_class
-
# and C.base_class would return B as the answer since A is an abstract_class.
-
1
def base_class
-
361
unless self < Base
-
raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
-
end
-
-
361
if superclass == Base || superclass.abstract_class?
-
361
self
-
else
-
superclass.base_class
-
end
-
end
-
-
# Set this to true if this is an abstract class (see <tt>abstract_class?</tt>).
-
# If you are using inheritance with ActiveRecord and don't want child classes
-
# to utilize the implied STI table name of the parent class, this will need to be true.
-
# For example, given the following:
-
#
-
# class SuperClass < ActiveRecord::Base
-
# self.abstract_class = true
-
# end
-
# class Child < SuperClass
-
# self.table_name = 'the_table_i_really_want'
-
# end
-
#
-
#
-
# <tt>self.abstract_class = true</tt> is required to make <tt>Child<.find,.create, or any Arel method></tt> use <tt>the_table_i_really_want</tt> instead of a table called <tt>super_classes</tt>
-
#
-
1
attr_accessor :abstract_class
-
-
# Returns whether this class is an abstract class or not.
-
1
def abstract_class?
-
149
defined?(@abstract_class) && @abstract_class == true
-
end
-
-
1
def sti_name
-
store_full_sti_class ? name : name.demodulize
-
end
-
-
1
protected
-
-
# Returns the class type of the record using the current module as a prefix. So descendants of
-
# MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
-
1
def compute_type(type_name)
-
1
if type_name.match(/^::/)
-
# If the type is prefixed with a scope operator then we assume that
-
# the type_name is an absolute reference.
-
ActiveSupport::Dependencies.constantize(type_name)
-
else
-
# Build a list of candidates to search for
-
1
candidates = []
-
2
name.scan(/::|$/) { candidates.unshift "#{$`}::#{type_name}" }
-
1
candidates << type_name
-
-
1
candidates.each do |candidate|
-
2
constant = ActiveSupport::Dependencies.safe_constantize(candidate)
-
2
return constant if candidate == constant.to_s
-
end
-
-
raise NameError.new("uninitialized constant #{candidates.first}", candidates.first)
-
end
-
end
-
-
1
private
-
-
# Called by +instantiate+ to decide which class to use for a new
-
# record instance. For single-table inheritance, we check the record
-
# for a +type+ column and return the corresponding class.
-
1
def discriminate_class_for_record(record)
-
27
if using_single_table_inheritance?(record)
-
find_sti_class(record[inheritance_column])
-
else
-
27
super
-
end
-
end
-
-
1
def using_single_table_inheritance?(record)
-
27
record[inheritance_column].present? && columns_hash.include?(inheritance_column)
-
end
-
-
1
def find_sti_class(type_name)
-
if store_full_sti_class
-
ActiveSupport::Dependencies.constantize(type_name)
-
else
-
compute_type(type_name)
-
end
-
rescue NameError
-
raise SubclassNotFound,
-
"The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " +
-
"This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
-
"Please rename this column if you didn't intend it to be used for storing the inheritance class " +
-
"or overwrite #{name}.inheritance_column to use another column for that information."
-
end
-
-
1
def type_condition(table = arel_table)
-
sti_column = table[inheritance_column]
-
sti_names = ([self] + descendants).map { |model| model.sti_name }
-
-
sti_column.in(sti_names)
-
end
-
-
# Detect the subclass from the inheritance column of attrs. If the inheritance column value
-
# is not self or a valid subclass, raises ActiveRecord::SubclassNotFound
-
# If this is a StrongParameters hash, and access to inheritance_column is not permitted,
-
# this will ignore the inheritance column and return nil
-
1
def subclass_from_attributes?(attrs)
-
43
columns_hash.include?(inheritance_column) && attrs.is_a?(Hash)
-
end
-
-
1
def subclass_from_attributes(attrs)
-
subclass_name = attrs.with_indifferent_access[inheritance_column]
-
-
if subclass_name.present? && subclass_name != self.name
-
subclass = subclass_name.safe_constantize
-
-
unless descendants.include?(subclass)
-
raise ActiveRecord::SubclassNotFound.new("Invalid single-table inheritance type: #{subclass_name} is not a subclass of #{name}")
-
end
-
-
subclass
-
end
-
end
-
end
-
-
1
def initialize_dup(other)
-
super
-
ensure_proper_type
-
end
-
-
1
private
-
-
1
def initialize_internals_callback
-
43
super
-
43
ensure_proper_type
-
end
-
-
# Sets the attribute used for single table inheritance to this class name if this is not the
-
# ActiveRecord::Base descendant.
-
# Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to
-
# do Reply.new without having to set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself.
-
# No such attribute would be set for objects of the Message class in that example.
-
1
def ensure_proper_type
-
43
klass = self.class
-
43
if klass.finder_needs_type_condition?
-
write_attribute(klass.inheritance_column, klass.sti_name)
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/string/filters'
-
-
1
module ActiveRecord
-
1
module Integration
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
##
-
# :singleton-method:
-
# Indicates the format used to generate the timestamp in the cache key.
-
# Accepts any of the symbols in <tt>Time::DATE_FORMATS</tt>.
-
#
-
# This is +:nsec+, by default.
-
1
class_attribute :cache_timestamp_format, :instance_writer => false
-
1
self.cache_timestamp_format = :nsec
-
end
-
-
# Returns a String, which Action Pack uses for constructing an URL to this
-
# object. The default implementation returns this record's id as a String,
-
# or nil if this record's unsaved.
-
#
-
# For example, suppose that you have a User model, and that you have a
-
# <tt>resources :users</tt> route. Normally, +user_path+ will
-
# construct a path with the user object's 'id' in it:
-
#
-
# user = User.find_by(name: 'Phusion')
-
# user_path(user) # => "/users/1"
-
#
-
# You can override +to_param+ in your model to make +user_path+ construct
-
# a path using the user's name instead of the user's id:
-
#
-
# class User < ActiveRecord::Base
-
# def to_param # overridden
-
# name
-
# end
-
# end
-
#
-
# user = User.find_by(name: 'Phusion')
-
# user_path(user) # => "/users/Phusion"
-
1
def to_param
-
# We can't use alias_method here, because method 'id' optimizes itself on the fly.
-
8
id && id.to_s # Be sure to stringify the id for routes
-
end
-
-
# Returns a cache key that can be used to identify this record.
-
#
-
# Product.new.cache_key # => "products/new"
-
# Product.find(5).cache_key # => "products/5" (updated_at not available)
-
# Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available)
-
#
-
# You can also pass a list of named timestamps, and the newest in the list will be
-
# used to generate the key:
-
#
-
# Person.find(5).cache_key(:updated_at, :last_reviewed_at)
-
1
def cache_key(*timestamp_names)
-
case
-
when new_record?
-
"#{model_name.cache_key}/new"
-
when timestamp_names.any?
-
timestamp = max_updated_column_timestamp(timestamp_names)
-
timestamp = timestamp.utc.to_s(cache_timestamp_format)
-
"#{model_name.cache_key}/#{id}-#{timestamp}"
-
when timestamp = max_updated_column_timestamp
-
timestamp = timestamp.utc.to_s(cache_timestamp_format)
-
"#{model_name.cache_key}/#{id}-#{timestamp}"
-
else
-
"#{model_name.cache_key}/#{id}"
-
end
-
end
-
-
1
module ClassMethods
-
# Defines your model's +to_param+ method to generate "pretty" URLs
-
# using +method_name+, which can be any attribute or method that
-
# responds to +to_s+.
-
#
-
# class User < ActiveRecord::Base
-
# to_param :name
-
# end
-
#
-
# user = User.find_by(name: 'Fancy Pants')
-
# user.id # => 123
-
# user_path(user) # => "/users/123-fancy-pants"
-
#
-
# Values longer than 20 characters will be truncated. The value
-
# is truncated word by word.
-
#
-
# user = User.find_by(name: 'David HeinemeierHansson')
-
# user.id # => 125
-
# user_path(user) # => "/users/125-david"
-
#
-
# Because the generated param begins with the record's +id+, it is
-
# suitable for passing to +find+. In a controller, for example:
-
#
-
# params[:id] # => "123-fancy-pants"
-
# User.find(params[:id]).id # => 123
-
1
def to_param(method_name = nil)
-
if method_name.nil?
-
super()
-
else
-
define_method :to_param do
-
if (default = super()) &&
-
(result = send(method_name).to_s).present? &&
-
(param = result.squish.truncate(20, separator: /\s/, omission: nil).parameterize).present?
-
"#{default}-#{param}"
-
else
-
default
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module LegacyYamlAdapter
-
1
def self.convert(klass, coder)
-
27
return coder unless coder.is_a?(Psych::Coder)
-
-
case coder["active_record_yaml_version"]
-
when 0 then coder
-
else
-
if coder["attributes"].is_a?(AttributeSet)
-
coder
-
else
-
Rails41.convert(klass, coder)
-
end
-
end
-
end
-
-
1
module Rails41
-
1
def self.convert(klass, coder)
-
attributes = klass.attributes_builder
-
.build_from_database(coder["attributes"])
-
new_record = coder["attributes"][klass.primary_key].blank?
-
-
{
-
"attributes" => attributes,
-
"new_record" => new_record,
-
}
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Locking
-
# == What is Optimistic Locking
-
#
-
# Optimistic locking allows multiple users to access the same record for edits, and assumes a minimum of
-
# conflicts with the data. It does this by checking whether another process has made changes to a record since
-
# it was opened, an <tt>ActiveRecord::StaleObjectError</tt> exception is thrown if that has occurred
-
# and the update is ignored.
-
#
-
# Check out <tt>ActiveRecord::Locking::Pessimistic</tt> for an alternative.
-
#
-
# == Usage
-
#
-
# Active Records support optimistic locking if the field +lock_version+ is present. Each update to the
-
# record increments the +lock_version+ column and the locking facilities ensure that records instantiated twice
-
# will let the last one saved raise a +StaleObjectError+ if the first was also updated. Example:
-
#
-
# p1 = Person.find(1)
-
# p2 = Person.find(1)
-
#
-
# p1.first_name = "Michael"
-
# p1.save
-
#
-
# p2.first_name = "should fail"
-
# p2.save # Raises a ActiveRecord::StaleObjectError
-
#
-
# Optimistic locking will also check for stale data when objects are destroyed. Example:
-
#
-
# p1 = Person.find(1)
-
# p2 = Person.find(1)
-
#
-
# p1.first_name = "Michael"
-
# p1.save
-
#
-
# p2.destroy # Raises a ActiveRecord::StaleObjectError
-
#
-
# You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
-
# or otherwise apply the business logic needed to resolve the conflict.
-
#
-
# This locking mechanism will function inside a single Ruby process. To make it work across all
-
# web requests, the recommended approach is to add +lock_version+ as a hidden field to your form.
-
#
-
# This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>.
-
# To override the name of the +lock_version+ column, set the <tt>locking_column</tt> class attribute:
-
#
-
# class Person < ActiveRecord::Base
-
# self.locking_column = :lock_person
-
# end
-
#
-
1
module Optimistic
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
class_attribute :lock_optimistically, instance_writer: false
-
1
self.lock_optimistically = true
-
end
-
-
1
def locking_enabled? #:nodoc:
-
33
self.class.locking_enabled?
-
end
-
-
1
private
-
1
def increment_lock
-
lock_col = self.class.locking_column
-
previous_lock_value = send(lock_col).to_i
-
send(lock_col + '=', previous_lock_value + 1)
-
end
-
-
1
def _create_record(attribute_names = self.attribute_names, *) # :nodoc:
-
28
if locking_enabled?
-
# We always want to persist the locking version, even if we don't detect
-
# a change from the default, since the database might have no default
-
attribute_names |= [self.class.locking_column]
-
end
-
28
super
-
end
-
-
1
def _update_record(attribute_names = self.attribute_names) #:nodoc:
-
3
return super unless locking_enabled?
-
return 0 if attribute_names.empty?
-
-
lock_col = self.class.locking_column
-
previous_lock_value = send(lock_col).to_i
-
increment_lock
-
-
attribute_names += [lock_col]
-
attribute_names.uniq!
-
-
begin
-
relation = self.class.unscoped
-
-
affected_rows = relation.where(
-
self.class.primary_key => id,
-
lock_col => previous_lock_value,
-
).update_all(
-
Hash[attributes_for_update(attribute_names).map do |name|
-
[name, _read_attribute(name)]
-
end]
-
)
-
-
unless affected_rows == 1
-
raise ActiveRecord::StaleObjectError.new(self, "update")
-
end
-
-
affected_rows
-
-
# If something went wrong, revert the version.
-
rescue Exception
-
send(lock_col + '=', previous_lock_value)
-
raise
-
end
-
end
-
-
1
def destroy_row
-
1
affected_rows = super
-
-
1
if locking_enabled? && affected_rows != 1
-
raise ActiveRecord::StaleObjectError.new(self, "destroy")
-
end
-
-
1
affected_rows
-
end
-
-
1
def relation_for_destroy
-
1
relation = super
-
-
1
if locking_enabled?
-
column_name = self.class.locking_column
-
column = self.class.columns_hash[column_name]
-
substitute = self.class.connection.substitute_at(column)
-
-
relation = relation.where(self.class.arel_table[column_name].eq(substitute))
-
relation.bind_values << [column, self[column_name].to_i]
-
end
-
-
1
relation
-
end
-
-
1
module ClassMethods
-
1
DEFAULT_LOCKING_COLUMN = 'lock_version'
-
-
# Returns true if the +lock_optimistically+ flag is set to true
-
# (which it is, by default) and the table includes the
-
# +locking_column+ column (defaults to +lock_version+).
-
1
def locking_enabled?
-
33
lock_optimistically && columns_hash[locking_column]
-
end
-
-
# Set the column to use for optimistic locking. Defaults to +lock_version+.
-
1
def locking_column=(value)
-
4
clear_caches_calculated_from_columns
-
4
@locking_column = value.to_s
-
end
-
-
# The version column used for optimistic locking. Defaults to +lock_version+.
-
1
def locking_column
-
68
reset_locking_column unless defined?(@locking_column)
-
68
@locking_column
-
end
-
-
# Reset the column used for optimistic locking back to the +lock_version+ default.
-
1
def reset_locking_column
-
4
self.locking_column = DEFAULT_LOCKING_COLUMN
-
end
-
-
# Make sure the lock version column gets updated when counters are
-
# updated.
-
1
def update_counters(id, counters)
-
counters = counters.merge(locking_column => 1) if locking_enabled?
-
super
-
end
-
-
1
private
-
-
# We need to apply this decorator here, rather than on module inclusion. The closure
-
# created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the
-
# sub class being decorated. As such, changes to `lock_optimistically`, or
-
# `locking_column` would not be picked up.
-
1
def inherited(subclass)
-
4
subclass.class_eval do
-
39
is_lock_column = ->(name, _) { lock_optimistically && name == locking_column }
-
4
decorate_matching_attribute_types(is_lock_column, :_optimistic_locking) do |type|
-
LockingType.new(type)
-
end
-
end
-
4
super
-
end
-
end
-
end
-
-
1
class LockingType < SimpleDelegator # :nodoc:
-
1
def type_cast_from_database(value)
-
# `nil` *should* be changed to 0
-
super.to_i
-
end
-
-
1
def init_with(coder)
-
__setobj__(coder['subtype'])
-
end
-
-
1
def encode_with(coder)
-
coder['subtype'] = __getobj__
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Locking
-
# Locking::Pessimistic provides support for row-level locking using
-
# SELECT ... FOR UPDATE and other lock types.
-
#
-
# Chain <tt>ActiveRecord::Base#find</tt> to <tt>ActiveRecord::QueryMethods#lock</tt> to obtain an exclusive
-
# lock on the selected rows:
-
# # select * from accounts where id=1 for update
-
# Account.lock.find(1)
-
#
-
# Call <tt>lock('some locking clause')</tt> to use a database-specific locking clause
-
# of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'. Example:
-
#
-
# Account.transaction do
-
# # select * from accounts where name = 'shugo' limit 1 for update
-
# shugo = Account.where("name = 'shugo'").lock(true).first
-
# yuko = Account.where("name = 'yuko'").lock(true).first
-
# shugo.balance -= 100
-
# shugo.save!
-
# yuko.balance += 100
-
# yuko.save!
-
# end
-
#
-
# You can also use <tt>ActiveRecord::Base#lock!</tt> method to lock one record by id.
-
# This may be better if you don't need to lock every row. Example:
-
#
-
# Account.transaction do
-
# # select * from accounts where ...
-
# accounts = Account.where(...)
-
# account1 = accounts.detect { |account| ... }
-
# account2 = accounts.detect { |account| ... }
-
# # select * from accounts where id=? for update
-
# account1.lock!
-
# account2.lock!
-
# account1.balance -= 100
-
# account1.save!
-
# account2.balance += 100
-
# account2.save!
-
# end
-
#
-
# You can start a transaction and acquire the lock in one go by calling
-
# <tt>with_lock</tt> with a block. The block is called from within
-
# a transaction, the object is already locked. Example:
-
#
-
# account = Account.first
-
# account.with_lock do
-
# # This block is called within a transaction,
-
# # account is already locked.
-
# account.balance -= 100
-
# account.save!
-
# end
-
#
-
# Database-specific information on row locking:
-
# MySQL: http://dev.mysql.com/doc/refman/5.1/en/innodb-locking-reads.html
-
# PostgreSQL: http://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
-
1
module Pessimistic
-
# Obtain a row lock on this record. Reloads the record to obtain the requested
-
# lock. Pass an SQL locking clause to append the end of the SELECT statement
-
# or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
-
# the locked record.
-
1
def lock!(lock = true)
-
reload(:lock => lock) if persisted?
-
self
-
end
-
-
# Wraps the passed block in a transaction, locking the object
-
# before yielding. You can pass the SQL locking clause
-
# as argument (see <tt>lock!</tt>).
-
1
def with_lock(lock = true)
-
transaction do
-
lock!(lock)
-
yield
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ModelSchema
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
##
-
# :singleton-method:
-
# Accessor for the prefix type that will be prepended to every primary key column name.
-
# The options are :table_name and :table_name_with_underscore. If the first is specified,
-
# the Product class will look for "productid" instead of "id" as the primary column. If the
-
# latter is specified, the Product class will look for "product_id" instead of "id". Remember
-
# that this is a global setting for all Active Records.
-
1
mattr_accessor :primary_key_prefix_type, instance_writer: false
-
-
##
-
# :singleton-method:
-
# Accessor for the name of the prefix string to prepend to every table name. So if set
-
# to "basecamp_", all table names will be named like "basecamp_projects", "basecamp_people",
-
# etc. This is a convenient way of creating a namespace for tables in a shared database.
-
# By default, the prefix is the empty string.
-
#
-
# If you are organising your models within modules you can add a prefix to the models within
-
# a namespace by defining a singleton method in the parent module called table_name_prefix which
-
# returns your chosen prefix.
-
1
class_attribute :table_name_prefix, instance_writer: false
-
1
self.table_name_prefix = ""
-
-
##
-
# :singleton-method:
-
# Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
-
# "people_basecamp"). By default, the suffix is the empty string.
-
#
-
# If you are organising your models within modules, you can add a suffix to the models within
-
# a namespace by defining a singleton method in the parent module called table_name_suffix which
-
# returns your chosen suffix.
-
1
class_attribute :table_name_suffix, instance_writer: false
-
1
self.table_name_suffix = ""
-
-
##
-
# :singleton-method:
-
# Accessor for the name of the schema migrations table. By default, the value is "schema_migrations"
-
1
class_attribute :schema_migrations_table_name, instance_accessor: false
-
1
self.schema_migrations_table_name = "schema_migrations"
-
-
##
-
# :singleton-method:
-
# Indicates whether table names should be the pluralized versions of the corresponding class names.
-
# If true, the default table name for a Product class will be +products+. If false, it would just be +product+.
-
# See table_name for the full rules on table/class naming. This is true, by default.
-
1
class_attribute :pluralize_table_names, instance_writer: false
-
1
self.pluralize_table_names = true
-
-
1
self.inheritance_column = 'type'
-
-
1
delegate :type_for_attribute, to: :class
-
end
-
-
# Derives the join table name for +first_table+ and +second_table+. The
-
# table names appear in alphabetical order. A common prefix is removed
-
# (useful for namespaced models like Music::Artist and Music::Record):
-
#
-
# artists, records => artists_records
-
# records, artists => artists_records
-
# music_artists, music_records => music_artists_records
-
1
def self.derive_join_table_name(first_table, second_table) # :nodoc:
-
[first_table.to_s, second_table.to_s].sort.join("\0").gsub(/^(.*_)(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_")
-
end
-
-
1
module ClassMethods
-
# Guesses the table name (in forced lower-case) based on the name of the class in the
-
# inheritance hierarchy descending directly from ActiveRecord::Base. So if the hierarchy
-
# looks like: Reply < Message < ActiveRecord::Base, then Message is used
-
# to guess the table name even when called on Reply. The rules used to do the guess
-
# are handled by the Inflector class in Active Support, which knows almost all common
-
# English inflections. You can add new inflections in config/initializers/inflections.rb.
-
#
-
# Nested classes are given table names prefixed by the singular form of
-
# the parent's table name. Enclosing modules are not considered.
-
#
-
# ==== Examples
-
#
-
# class Invoice < ActiveRecord::Base
-
# end
-
#
-
# file class table_name
-
# invoice.rb Invoice invoices
-
#
-
# class Invoice < ActiveRecord::Base
-
# class Lineitem < ActiveRecord::Base
-
# end
-
# end
-
#
-
# file class table_name
-
# invoice.rb Invoice::Lineitem invoice_lineitems
-
#
-
# module Invoice
-
# class Lineitem < ActiveRecord::Base
-
# end
-
# end
-
#
-
# file class table_name
-
# invoice/lineitem.rb Invoice::Lineitem lineitems
-
#
-
# Additionally, the class-level +table_name_prefix+ is prepended and the
-
# +table_name_suffix+ is appended. So if you have "myapp_" as a prefix,
-
# the table name guess for an Invoice class becomes "myapp_invoices".
-
# Invoice::Lineitem becomes "myapp_invoice_lineitems".
-
#
-
# You can also set your own table name explicitly:
-
#
-
# class Mouse < ActiveRecord::Base
-
# self.table_name = "mice"
-
# end
-
#
-
# Alternatively, you can override the table_name method to define your
-
# own computation. (Possibly using <tt>super</tt> to manipulate the default
-
# table name.) Example:
-
#
-
# class Post < ActiveRecord::Base
-
# def self.table_name
-
# "special_" + super
-
# end
-
# end
-
# Post.table_name # => "special_posts"
-
1
def table_name
-
238
reset_table_name unless defined?(@table_name)
-
238
@table_name
-
end
-
-
# Sets the table name explicitly. Example:
-
#
-
# class Project < ActiveRecord::Base
-
# self.table_name = "project"
-
# end
-
#
-
# You can also just define your own <tt>self.table_name</tt> method; see
-
# the documentation for ActiveRecord::Base#table_name.
-
1
def table_name=(value)
-
3
value = value && value.to_s
-
-
3
if defined?(@table_name)
-
return if value == @table_name
-
reset_column_information if connected?
-
end
-
-
3
@table_name = value
-
3
@quoted_table_name = nil
-
3
@arel_table = nil
-
3
@sequence_name = nil unless defined?(@explicit_sequence_name) && @explicit_sequence_name
-
3
@relation = Relation.create(self, arel_table)
-
end
-
-
# Returns a quoted version of the table name, used to construct SQL statements.
-
1
def quoted_table_name
-
@quoted_table_name ||= connection.quote_table_name(table_name)
-
end
-
-
# Computes the table name, (re)sets it internally, and returns it.
-
1
def reset_table_name #:nodoc:
-
3
self.table_name = if abstract_class?
-
superclass == Base ? nil : superclass.table_name
-
elsif superclass.abstract_class?
-
superclass.table_name || compute_table_name
-
else
-
3
compute_table_name
-
end
-
end
-
-
1
def full_table_name_prefix #:nodoc:
-
6
(parents.detect{ |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
-
end
-
-
1
def full_table_name_suffix #:nodoc:
-
6
(parents.detect {|p| p.respond_to?(:table_name_suffix) } || self).table_name_suffix
-
end
-
-
# Defines the name of the table column which will store the class name on single-table
-
# inheritance situations.
-
#
-
# The default inheritance column name is +type+, which means it's a
-
# reserved word inside Active Record. To be able to use single-table
-
# inheritance with another column name, or to use the column +type+ in
-
# your own model for something else, you can set +inheritance_column+:
-
#
-
# self.inheritance_column = 'zoink'
-
1
def inheritance_column
-
172
(@inheritance_column ||= nil) || superclass.inheritance_column
-
end
-
-
# Sets the value of inheritance_column
-
1
def inheritance_column=(value)
-
1
@inheritance_column = value.to_s
-
1
@explicit_inheritance_column = true
-
end
-
-
1
def sequence_name
-
if base_class == self
-
@sequence_name ||= reset_sequence_name
-
else
-
(@sequence_name ||= nil) || base_class.sequence_name
-
end
-
end
-
-
1
def reset_sequence_name #:nodoc:
-
@explicit_sequence_name = false
-
@sequence_name = connection.default_sequence_name(table_name, primary_key)
-
end
-
-
# Sets the name of the sequence to use when generating ids to the given
-
# value, or (if the value is nil or false) to the value returned by the
-
# given block. This is required for Oracle and is useful for any
-
# database which relies on sequences for primary key generation.
-
#
-
# If a sequence name is not explicitly set when using Oracle,
-
# it will default to the commonly used pattern of: #{table_name}_seq
-
#
-
# If a sequence name is not explicitly set when using PostgreSQL, it
-
# will discover the sequence corresponding to your primary key for you.
-
#
-
# class Project < ActiveRecord::Base
-
# self.sequence_name = "projectseq" # default would have been "project_seq"
-
# end
-
1
def sequence_name=(value)
-
@sequence_name = value.to_s
-
@explicit_sequence_name = true
-
end
-
-
# Indicates whether the table associated with this class exists
-
1
def table_exists?
-
6
connection.schema_cache.table_exists?(table_name)
-
end
-
-
1
def attributes_builder # :nodoc:
-
30
@attributes_builder ||= AttributeSet::Builder.new(column_types, primary_key)
-
end
-
-
1
def column_types # :nodoc:
-
@column_types ||= columns_hash.transform_values(&:cast_type).tap do |h|
-
4
h.default = Type::Value.new
-
11
end
-
end
-
-
1
def type_for_attribute(attr_name) # :nodoc:
-
column_types[attr_name]
-
end
-
-
# Returns a hash where the keys are column names and the values are
-
# default values when instantiating the AR object for this table.
-
1
def column_defaults
-
_default_attributes.to_hash
-
end
-
-
1
def _default_attributes # :nodoc:
-
@default_attributes ||= attributes_builder.build_from_database(
-
43
raw_default_values)
-
end
-
-
# Returns an array of column names as strings.
-
1
def column_names
-
47
@column_names ||= columns.map { |column| column.name }
-
end
-
-
# Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
-
# and columns used for single table inheritance have been removed.
-
1
def content_columns
-
@content_columns ||= columns.reject { |c| c.name == primary_key || c.name =~ /(_id|_count)$/ || c.name == inheritance_column }
-
end
-
-
# Resets all the cached information about columns, which will cause them
-
# to be reloaded on the next request.
-
#
-
# The most common usage pattern for this method is probably in a migration,
-
# when just after creating a table you want to populate it with some default
-
# values, eg:
-
#
-
# class CreateJobLevels < ActiveRecord::Migration
-
# def up
-
# create_table :job_levels do |t|
-
# t.integer :id
-
# t.string :name
-
#
-
# t.timestamps
-
# end
-
#
-
# JobLevel.reset_column_information
-
# %w{assistant executive manager director}.each do |type|
-
# JobLevel.create(name: type)
-
# end
-
# end
-
#
-
# def down
-
# drop_table :job_levels
-
# end
-
# end
-
1
def reset_column_information
-
connection.clear_cache!
-
undefine_attribute_methods
-
connection.schema_cache.clear_table_cache!(table_name)
-
-
@arel_engine = nil
-
@column_names = nil
-
@column_types = nil
-
@content_columns = nil
-
@default_attributes = nil
-
@inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column
-
@relation = nil
-
end
-
-
1
private
-
-
# Guesses the table name, but does not decorate it with prefix and suffix information.
-
1
def undecorated_table_name(class_name = base_class.name)
-
3
table_name = class_name.to_s.demodulize.underscore
-
3
pluralize_table_names ? table_name.pluralize : table_name
-
end
-
-
# Computes and returns a table name according to default conventions.
-
1
def compute_table_name
-
3
base = base_class
-
3
if self == base
-
# Nested classes are prefixed with singular parent table name.
-
3
if parent < Base && !parent.abstract_class?
-
contained = parent.table_name
-
contained = contained.singularize if parent.pluralize_table_names
-
contained += '_'
-
end
-
-
3
"#{full_table_name_prefix}#{contained}#{undecorated_table_name(name)}#{full_table_name_suffix}"
-
else
-
# STI subclasses always use their superclass' table.
-
base.table_name
-
end
-
end
-
-
1
def raw_default_values
-
3
columns_hash.transform_values(&:default)
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/hash/except'
-
1
require 'active_support/core_ext/object/try'
-
1
require 'active_support/core_ext/hash/indifferent_access'
-
-
1
module ActiveRecord
-
1
module NestedAttributes #:nodoc:
-
1
class TooManyRecords < ActiveRecordError
-
end
-
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
class_attribute :nested_attributes_options, instance_writer: false
-
1
self.nested_attributes_options = {}
-
end
-
-
# = Active Record Nested Attributes
-
#
-
# Nested attributes allow you to save attributes on associated records
-
# through the parent. By default nested attribute updating is turned off
-
# and you can enable it using the accepts_nested_attributes_for class
-
# method. When you enable nested attributes an attribute writer is
-
# defined on the model.
-
#
-
# The attribute writer is named after the association, which means that
-
# in the following example, two new methods are added to your model:
-
#
-
# <tt>author_attributes=(attributes)</tt> and
-
# <tt>pages_attributes=(attributes)</tt>.
-
#
-
# class Book < ActiveRecord::Base
-
# has_one :author
-
# has_many :pages
-
#
-
# accepts_nested_attributes_for :author, :pages
-
# end
-
#
-
# Note that the <tt>:autosave</tt> option is automatically enabled on every
-
# association that accepts_nested_attributes_for is used for.
-
#
-
# === One-to-one
-
#
-
# Consider a Member model that has one Avatar:
-
#
-
# class Member < ActiveRecord::Base
-
# has_one :avatar
-
# accepts_nested_attributes_for :avatar
-
# end
-
#
-
# Enabling nested attributes on a one-to-one association allows you to
-
# create the member and avatar in one go:
-
#
-
# params = { member: { name: 'Jack', avatar_attributes: { icon: 'smiling' } } }
-
# member = Member.create(params[:member])
-
# member.avatar.id # => 2
-
# member.avatar.icon # => 'smiling'
-
#
-
# It also allows you to update the avatar through the member:
-
#
-
# params = { member: { avatar_attributes: { id: '2', icon: 'sad' } } }
-
# member.update params[:member]
-
# member.avatar.icon # => 'sad'
-
#
-
# By default you will only be able to set and update attributes on the
-
# associated model. If you want to destroy the associated model through the
-
# attributes hash, you have to enable it first using the
-
# <tt>:allow_destroy</tt> option.
-
#
-
# class Member < ActiveRecord::Base
-
# has_one :avatar
-
# accepts_nested_attributes_for :avatar, allow_destroy: true
-
# end
-
#
-
# Now, when you add the <tt>_destroy</tt> key to the attributes hash, with a
-
# value that evaluates to +true+, you will destroy the associated model:
-
#
-
# member.avatar_attributes = { id: '2', _destroy: '1' }
-
# member.avatar.marked_for_destruction? # => true
-
# member.save
-
# member.reload.avatar # => nil
-
#
-
# Note that the model will _not_ be destroyed until the parent is saved.
-
#
-
# === One-to-many
-
#
-
# Consider a member that has a number of posts:
-
#
-
# class Member < ActiveRecord::Base
-
# has_many :posts
-
# accepts_nested_attributes_for :posts
-
# end
-
#
-
# You can now set or update attributes on the associated posts through
-
# an attribute hash for a member: include the key +:posts_attributes+
-
# with an array of hashes of post attributes as a value.
-
#
-
# For each hash that does _not_ have an <tt>id</tt> key a new record will
-
# be instantiated, unless the hash also contains a <tt>_destroy</tt> key
-
# that evaluates to +true+.
-
#
-
# params = { member: {
-
# name: 'joe', posts_attributes: [
-
# { title: 'Kari, the awesome Ruby documentation browser!' },
-
# { title: 'The egalitarian assumption of the modern citizen' },
-
# { title: '', _destroy: '1' } # this will be ignored
-
# ]
-
# }}
-
#
-
# member = Member.create(params[:member])
-
# member.posts.length # => 2
-
# member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
-
# member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
-
#
-
# You may also set a :reject_if proc to silently ignore any new record
-
# hashes if they fail to pass your criteria. For example, the previous
-
# example could be rewritten as:
-
#
-
# class Member < ActiveRecord::Base
-
# has_many :posts
-
# accepts_nested_attributes_for :posts, reject_if: proc { |attributes| attributes['title'].blank? }
-
# end
-
#
-
# params = { member: {
-
# name: 'joe', posts_attributes: [
-
# { title: 'Kari, the awesome Ruby documentation browser!' },
-
# { title: 'The egalitarian assumption of the modern citizen' },
-
# { title: '' } # this will be ignored because of the :reject_if proc
-
# ]
-
# }}
-
#
-
# member = Member.create(params[:member])
-
# member.posts.length # => 2
-
# member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
-
# member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
-
#
-
# Alternatively, :reject_if also accepts a symbol for using methods:
-
#
-
# class Member < ActiveRecord::Base
-
# has_many :posts
-
# accepts_nested_attributes_for :posts, reject_if: :new_record?
-
# end
-
#
-
# class Member < ActiveRecord::Base
-
# has_many :posts
-
# accepts_nested_attributes_for :posts, reject_if: :reject_posts
-
#
-
# def reject_posts(attributed)
-
# attributed['title'].blank?
-
# end
-
# end
-
#
-
# If the hash contains an <tt>id</tt> key that matches an already
-
# associated record, the matching record will be modified:
-
#
-
# member.attributes = {
-
# name: 'Joe',
-
# posts_attributes: [
-
# { id: 1, title: '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!' },
-
# { id: 2, title: '[UPDATED] other post' }
-
# ]
-
# }
-
#
-
# member.posts.first.title # => '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!'
-
# member.posts.second.title # => '[UPDATED] other post'
-
#
-
# By default the associated records are protected from being destroyed. If
-
# you want to destroy any of the associated records through the attributes
-
# hash, you have to enable it first using the <tt>:allow_destroy</tt>
-
# option. This will allow you to also use the <tt>_destroy</tt> key to
-
# destroy existing records:
-
#
-
# class Member < ActiveRecord::Base
-
# has_many :posts
-
# accepts_nested_attributes_for :posts, allow_destroy: true
-
# end
-
#
-
# params = { member: {
-
# posts_attributes: [{ id: '2', _destroy: '1' }]
-
# }}
-
#
-
# member.attributes = params[:member]
-
# member.posts.detect { |p| p.id == 2 }.marked_for_destruction? # => true
-
# member.posts.length # => 2
-
# member.save
-
# member.reload.posts.length # => 1
-
#
-
# Nested attributes for an associated collection can also be passed in
-
# the form of a hash of hashes instead of an array of hashes:
-
#
-
# Member.create(name: 'joe',
-
# posts_attributes: { first: { title: 'Foo' },
-
# second: { title: 'Bar' } })
-
#
-
# has the same effect as
-
#
-
# Member.create(name: 'joe',
-
# posts_attributes: [ { title: 'Foo' },
-
# { title: 'Bar' } ])
-
#
-
# The keys of the hash which is the value for +:posts_attributes+ are
-
# ignored in this case.
-
# However, it is not allowed to use +'id'+ or +:id+ for one of
-
# such keys, otherwise the hash will be wrapped in an array and
-
# interpreted as an attribute hash for a single post.
-
#
-
# Passing attributes for an associated collection in the form of a hash
-
# of hashes can be used with hashes generated from HTTP/HTML parameters,
-
# where there maybe no natural way to submit an array of hashes.
-
#
-
# === Saving
-
#
-
# All changes to models, including the destruction of those marked for
-
# destruction, are saved and destroyed automatically and atomically when
-
# the parent model is saved. This happens inside the transaction initiated
-
# by the parents save method. See ActiveRecord::AutosaveAssociation.
-
#
-
# === Validating the presence of a parent model
-
#
-
# If you want to validate that a child record is associated with a parent
-
# record, you can use <tt>validates_presence_of</tt> and
-
# <tt>inverse_of</tt> as this example illustrates:
-
#
-
# class Member < ActiveRecord::Base
-
# has_many :posts, inverse_of: :member
-
# accepts_nested_attributes_for :posts
-
# end
-
#
-
# class Post < ActiveRecord::Base
-
# belongs_to :member, inverse_of: :posts
-
# validates_presence_of :member
-
# end
-
#
-
# Note that if you do not specify the <tt>inverse_of</tt> option, then
-
# Active Record will try to automatically guess the inverse association
-
# based on heuristics.
-
#
-
# For one-to-one nested associations, if you build the new (in-memory)
-
# child object yourself before assignment, then this module will not
-
# overwrite it, e.g.:
-
#
-
# class Member < ActiveRecord::Base
-
# has_one :avatar
-
# accepts_nested_attributes_for :avatar
-
#
-
# def avatar
-
# super || build_avatar(width: 200)
-
# end
-
# end
-
#
-
# member = Member.new
-
# member.avatar_attributes = {icon: 'sad'}
-
# member.avatar.width # => 200
-
1
module ClassMethods
-
1
REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == '_destroy' || value.blank? } }
-
-
# Defines an attributes writer for the specified association(s).
-
#
-
# Supported options:
-
# [:allow_destroy]
-
# If true, destroys any members from the attributes hash with a
-
# <tt>_destroy</tt> key and a value that evaluates to +true+
-
# (eg. 1, '1', true, or 'true'). This option is off by default.
-
# [:reject_if]
-
# Allows you to specify a Proc or a Symbol pointing to a method
-
# that checks whether a record should be built for a certain attribute
-
# hash. The hash is passed to the supplied Proc or the method
-
# and it should return either +true+ or +false+. When no :reject_if
-
# is specified, a record will be built for all attribute hashes that
-
# do not have a <tt>_destroy</tt> value that evaluates to true.
-
# Passing <tt>:all_blank</tt> instead of a Proc will create a proc
-
# that will reject a record where all the attributes are blank excluding
-
# any value for _destroy.
-
# [:limit]
-
# Allows you to specify the maximum number of the associated records that
-
# can be processed with the nested attributes. Limit also can be specified as a
-
# Proc or a Symbol pointing to a method that should return number. If the size of the
-
# nested attributes array exceeds the specified limit, NestedAttributes::TooManyRecords
-
# exception is raised. If omitted, any number associations can be processed.
-
# Note that the :limit option is only applicable to one-to-many associations.
-
# [:update_only]
-
# For a one-to-one association, this option allows you to specify how
-
# nested attributes are to be used when an associated record already
-
# exists. In general, an existing record may either be updated with the
-
# new set of attribute values or be replaced by a wholly new record
-
# containing those values. By default the :update_only option is +false+
-
# and the nested attributes are used to update the existing record only
-
# if they include the record's <tt>:id</tt> value. Otherwise a new
-
# record will be instantiated and used to replace the existing one.
-
# However if the :update_only option is +true+, the nested attributes
-
# are used to update the record's attributes always, regardless of
-
# whether the <tt>:id</tt> is present. The option is ignored for collection
-
# associations.
-
#
-
# Examples:
-
# # creates avatar_attributes=
-
# accepts_nested_attributes_for :avatar, reject_if: proc { |attributes| attributes['name'].blank? }
-
# # creates avatar_attributes=
-
# accepts_nested_attributes_for :avatar, reject_if: :all_blank
-
# # creates avatar_attributes= and posts_attributes=
-
# accepts_nested_attributes_for :avatar, :posts, allow_destroy: true
-
1
def accepts_nested_attributes_for(*attr_names)
-
options = { :allow_destroy => false, :update_only => false }
-
options.update(attr_names.extract_options!)
-
options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only)
-
options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
-
-
attr_names.each do |association_name|
-
if reflection = _reflect_on_association(association_name)
-
reflection.autosave = true
-
define_autosave_validation_callbacks(reflection)
-
-
nested_attributes_options = self.nested_attributes_options.dup
-
nested_attributes_options[association_name.to_sym] = options
-
self.nested_attributes_options = nested_attributes_options
-
-
type = (reflection.collection? ? :collection : :one_to_one)
-
generate_association_writer(association_name, type)
-
else
-
raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
-
end
-
end
-
end
-
-
1
private
-
-
# Generates a writer method for this association. Serves as a point for
-
# accessing the objects in the association. For example, this method
-
# could generate the following:
-
#
-
# def pirate_attributes=(attributes)
-
# assign_nested_attributes_for_one_to_one_association(:pirate, attributes)
-
# end
-
#
-
# This redirects the attempts to write objects in an association through
-
# the helper methods defined below. Makes it seem like the nested
-
# associations are just regular associations.
-
1
def generate_association_writer(association_name, type)
-
generated_association_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1
-
if method_defined?(:#{association_name}_attributes=)
-
remove_method(:#{association_name}_attributes=)
-
end
-
def #{association_name}_attributes=(attributes)
-
assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes)
-
end
-
eoruby
-
end
-
end
-
-
# Returns ActiveRecord::AutosaveAssociation::marked_for_destruction? It's
-
# used in conjunction with fields_for to build a form element for the
-
# destruction of this association.
-
#
-
# See ActionView::Helpers::FormHelper::fields_for for more info.
-
1
def _destroy
-
marked_for_destruction?
-
end
-
-
1
private
-
-
# Attribute hash keys that should not be assigned as normal attributes.
-
# These hash keys are nested attributes implementation details.
-
1
UNASSIGNABLE_KEYS = %w( id _destroy )
-
-
# Assigns the given attributes to the association.
-
#
-
# If an associated record does not yet exist, one will be instantiated. If
-
# an associated record already exists, the method's behavior depends on
-
# the value of the update_only option. If update_only is +false+ and the
-
# given attributes include an <tt>:id</tt> that matches the existing record's
-
# id, then the existing record will be modified. If no <tt>:id</tt> is provided
-
# it will be replaced with a new record. If update_only is +true+ the existing
-
# record will be modified regardless of whether an <tt>:id</tt> is provided.
-
#
-
# If the given attributes include a matching <tt>:id</tt> attribute, or
-
# update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
-
# then the existing record will be marked for destruction.
-
1
def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
-
options = self.nested_attributes_options[association_name]
-
attributes = attributes.with_indifferent_access
-
existing_record = send(association_name)
-
-
if (options[:update_only] || !attributes['id'].blank?) && existing_record &&
-
(options[:update_only] || existing_record.id.to_s == attributes['id'].to_s)
-
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes)
-
-
elsif attributes['id'].present?
-
raise_nested_attributes_record_not_found!(association_name, attributes['id'])
-
-
elsif !reject_new_record?(association_name, attributes)
-
assignable_attributes = attributes.except(*UNASSIGNABLE_KEYS)
-
-
if existing_record && existing_record.new_record?
-
existing_record.assign_attributes(assignable_attributes)
-
association(association_name).initialize_attributes(existing_record)
-
else
-
method = "build_#{association_name}"
-
if respond_to?(method)
-
send(method, assignable_attributes)
-
else
-
raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?"
-
end
-
end
-
end
-
end
-
-
# Assigns the given attributes to the collection association.
-
#
-
# Hashes with an <tt>:id</tt> value matching an existing associated record
-
# will update that record. Hashes without an <tt>:id</tt> value will build
-
# a new record for the association. Hashes with a matching <tt>:id</tt>
-
# value and a <tt>:_destroy</tt> key set to a truthy value will mark the
-
# matched record for destruction.
-
#
-
# For example:
-
#
-
# assign_nested_attributes_for_collection_association(:people, {
-
# '1' => { id: '1', name: 'Peter' },
-
# '2' => { name: 'John' },
-
# '3' => { id: '2', _destroy: true }
-
# })
-
#
-
# Will update the name of the Person with ID 1, build a new associated
-
# person with the name 'John', and mark the associated Person with ID 2
-
# for destruction.
-
#
-
# Also accepts an Array of attribute hashes:
-
#
-
# assign_nested_attributes_for_collection_association(:people, [
-
# { id: '1', name: 'Peter' },
-
# { name: 'John' },
-
# { id: '2', _destroy: true }
-
# ])
-
1
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
-
options = self.nested_attributes_options[association_name]
-
-
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
-
raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
-
end
-
-
check_record_limit!(options[:limit], attributes_collection)
-
-
if attributes_collection.is_a? Hash
-
keys = attributes_collection.keys
-
attributes_collection = if keys.include?('id') || keys.include?(:id)
-
[attributes_collection]
-
else
-
attributes_collection.values
-
end
-
end
-
-
association = association(association_name)
-
-
existing_records = if association.loaded?
-
association.target
-
else
-
attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact
-
attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids)
-
end
-
-
attributes_collection.each do |attributes|
-
attributes = attributes.with_indifferent_access
-
-
if attributes['id'].blank?
-
unless reject_new_record?(association_name, attributes)
-
association.build(attributes.except(*UNASSIGNABLE_KEYS))
-
end
-
elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
-
unless call_reject_if(association_name, attributes)
-
# Make sure we are operating on the actual object which is in the association's
-
# proxy_target array (either by finding it, or adding it if not found)
-
# Take into account that the proxy_target may have changed due to callbacks
-
target_record = association.target.detect { |record| record.id.to_s == attributes['id'].to_s }
-
if target_record
-
existing_record = target_record
-
else
-
association.add_to_target(existing_record, :skip_callbacks)
-
end
-
-
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
-
end
-
else
-
raise_nested_attributes_record_not_found!(association_name, attributes['id'])
-
end
-
end
-
end
-
-
# Takes in a limit and checks if the attributes_collection has too many
-
# records. It accepts limit in the form of symbol, proc, or
-
# number-like object (anything that can be compared with an integer).
-
#
-
# Raises TooManyRecords error if the attributes_collection is
-
# larger than the limit.
-
1
def check_record_limit!(limit, attributes_collection)
-
if limit
-
limit = case limit
-
when Symbol
-
send(limit)
-
when Proc
-
limit.call
-
else
-
limit
-
end
-
-
if limit && attributes_collection.size > limit
-
raise TooManyRecords, "Maximum #{limit} records are allowed. Got #{attributes_collection.size} records instead."
-
end
-
end
-
end
-
-
# Updates a record with the +attributes+ or marks it for destruction if
-
# +allow_destroy+ is +true+ and has_destroy_flag? returns +true+.
-
1
def assign_to_or_mark_for_destruction(record, attributes, allow_destroy)
-
record.assign_attributes(attributes.except(*UNASSIGNABLE_KEYS))
-
record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy
-
end
-
-
# Determines if a hash contains a truthy _destroy key.
-
1
def has_destroy_flag?(hash)
-
Type::Boolean.new.type_cast_from_user(hash['_destroy'])
-
end
-
-
# Determines if a new record should be rejected by checking
-
# has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
-
# association and evaluates to +true+.
-
1
def reject_new_record?(association_name, attributes)
-
has_destroy_flag?(attributes) || call_reject_if(association_name, attributes)
-
end
-
-
# Determines if a record with the particular +attributes+ should be
-
# rejected by calling the reject_if Symbol or Proc (if defined).
-
# The reject_if option is defined by +accepts_nested_attributes_for+.
-
#
-
# Returns false if there is a +destroy_flag+ on the attributes.
-
1
def call_reject_if(association_name, attributes)
-
return false if has_destroy_flag?(attributes)
-
case callback = self.nested_attributes_options[association_name][:reject_if]
-
when Symbol
-
method(callback).arity == 0 ? send(callback) : send(callback, attributes)
-
when Proc
-
callback.call(attributes)
-
end
-
end
-
-
1
def raise_nested_attributes_record_not_found!(association_name, record_id)
-
raise RecordNotFound, "Couldn't find #{self.class._reflect_on_association(association_name).klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
-
end
-
end
-
end
-
1
module ActiveRecord
-
# = Active Record No Touching
-
1
module NoTouching
-
1
extend ActiveSupport::Concern
-
-
1
module ClassMethods
-
# Lets you selectively disable calls to `touch` for the
-
# duration of a block.
-
#
-
# ==== Examples
-
# ActiveRecord::Base.no_touching do
-
# Project.first.touch # does nothing
-
# Message.first.touch # does nothing
-
# end
-
#
-
# Project.no_touching do
-
# Project.first.touch # does nothing
-
# Message.first.touch # works, but does not touch the associated project
-
# end
-
#
-
1
def no_touching(&block)
-
NoTouching.apply_to(self, &block)
-
end
-
end
-
-
1
class << self
-
1
def apply_to(klass) #:nodoc:
-
klasses.push(klass)
-
yield
-
ensure
-
klasses.pop
-
end
-
-
1
def applied_to?(klass) #:nodoc:
-
klasses.any? { |k| k >= klass }
-
end
-
-
1
private
-
1
def klasses
-
Thread.current[:no_touching_classes] ||= []
-
end
-
end
-
-
1
def no_touching?
-
NoTouching.applied_to?(self.class)
-
end
-
-
1
def touch(*) # :nodoc:
-
super unless no_touching?
-
end
-
end
-
end
-
# -*- coding: utf-8 -*-
-
-
1
module ActiveRecord
-
1
module NullRelation # :nodoc:
-
1
def exec_queries
-
@records = []
-
end
-
-
1
def pluck(*column_names)
-
[]
-
end
-
-
1
def delete_all(_conditions = nil)
-
0
-
end
-
-
1
def update_all(_updates, _conditions = nil, _options = {})
-
0
-
end
-
-
1
def delete(_id_or_array)
-
0
-
end
-
-
1
def size
-
calculate :size, nil
-
end
-
-
1
def empty?
-
true
-
end
-
-
1
def any?
-
false
-
end
-
-
1
def many?
-
false
-
end
-
-
1
def to_sql
-
""
-
end
-
-
1
def count(*)
-
calculate :count, nil
-
end
-
-
1
def sum(*)
-
calculate :sum, nil
-
end
-
-
1
def average(*)
-
calculate :average, nil
-
end
-
-
1
def minimum(*)
-
calculate :minimum, nil
-
end
-
-
1
def maximum(*)
-
calculate :maximum, nil
-
end
-
-
1
def calculate(operation, _column_name, _options = {})
-
# TODO: Remove _options argument as soon we remove support to
-
# activerecord-deprecated_finders.
-
if [:count, :sum, :size].include? operation
-
group_values.any? ? Hash.new : 0
-
elsif [:average, :minimum, :maximum].include?(operation) && group_values.any?
-
Hash.new
-
else
-
nil
-
end
-
end
-
-
1
def exists?(_id = false)
-
false
-
end
-
end
-
end
-
1
module ActiveRecord
-
# = Active Record Persistence
-
1
module Persistence
-
1
extend ActiveSupport::Concern
-
-
1
module ClassMethods
-
# Creates an object (or multiple objects) and saves it to the database, if validations pass.
-
# The resulting object is returned whether the object was saved successfully to the database or not.
-
#
-
# The +attributes+ parameter can be either a Hash or an Array of Hashes. These Hashes describe the
-
# attributes on the objects that are to be created.
-
#
-
# ==== Examples
-
# # Create a single new object
-
# User.create(first_name: 'Jamie')
-
#
-
# # Create an Array of new objects
-
# User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }])
-
#
-
# # Create a single object and pass it into a block to set other attributes.
-
# User.create(first_name: 'Jamie') do |u|
-
# u.is_admin = false
-
# end
-
#
-
# # Creating an Array of new objects using a block, where the block is executed for each object:
-
# User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) do |u|
-
# u.is_admin = false
-
# end
-
1
def create(attributes = nil, &block)
-
10
if attributes.is_a?(Array)
-
attributes.collect { |attr| create(attr, &block) }
-
else
-
10
object = new(attributes, &block)
-
10
object.save
-
10
object
-
end
-
end
-
-
# Creates an object (or multiple objects) and saves it to the database,
-
# if validations pass. Raises a RecordInvalid error if validations fail,
-
# unlike Base#create.
-
#
-
# The +attributes+ parameter can be either a Hash or an Array of Hashes.
-
# These describe which attributes to be created on the object, or
-
# multiple objects when given an Array of Hashes.
-
1
def create!(attributes = nil, &block)
-
if attributes.is_a?(Array)
-
attributes.collect { |attr| create!(attr, &block) }
-
else
-
object = new(attributes, &block)
-
object.save!
-
object
-
end
-
end
-
-
# Given an attributes hash, +instantiate+ returns a new instance of
-
# the appropriate class. Accepts only keys as strings.
-
#
-
# For example, +Post.all+ may return Comments, Messages, and Emails
-
# by storing the record's subclass in a +type+ attribute. By calling
-
# +instantiate+ instead of +new+, finder methods ensure they get new
-
# instances of the appropriate class for each record.
-
#
-
# See +ActiveRecord::Inheritance#discriminate_class_for_record+ to see
-
# how this "single-table" inheritance mapping is implemented.
-
1
def instantiate(attributes, column_types = {})
-
27
klass = discriminate_class_for_record(attributes)
-
27
attributes = klass.attributes_builder.build_from_database(attributes, column_types)
-
27
klass.allocate.init_with('attributes' => attributes, 'new_record' => false)
-
end
-
-
1
private
-
# Called by +instantiate+ to decide which class to use for a new
-
# record instance.
-
#
-
# See +ActiveRecord::Inheritance#discriminate_class_for_record+ for
-
# the single-table inheritance discriminator.
-
1
def discriminate_class_for_record(record)
-
27
self
-
end
-
end
-
-
# Returns true if this object hasn't been saved yet -- that is, a record
-
# for the object doesn't exist in the database yet; otherwise, returns false.
-
1
def new_record?
-
141
sync_with_transaction_state
-
141
@new_record
-
end
-
-
# Returns true if this object has been destroyed, otherwise returns false.
-
1
def destroyed?
-
8
sync_with_transaction_state
-
8
@destroyed
-
end
-
-
# Returns true if the record is persisted, i.e. it's not a new record and it was
-
# not destroyed, otherwise returns false.
-
1
def persisted?
-
43
!(new_record? || destroyed?)
-
end
-
-
# Saves the model.
-
#
-
# If the model is new a record gets created in the database, otherwise
-
# the existing record gets updated.
-
#
-
# By default, save always run validations. If any of them fail the action
-
# is cancelled and +save+ returns +false+. However, if you supply
-
# validate: false, validations are bypassed altogether. See
-
# ActiveRecord::Validations for more information.
-
#
-
# There's a series of callbacks associated with +save+. If any of the
-
# <tt>before_*</tt> callbacks return +false+ the action is cancelled and
-
# +save+ returns +false+. See ActiveRecord::Callbacks for further
-
# details.
-
#
-
# Attributes marked as readonly are silently ignored if the record is
-
# being updated.
-
1
def save(*)
-
14
create_or_update
-
rescue ActiveRecord::RecordInvalid
-
false
-
end
-
-
# Saves the model.
-
#
-
# If the model is new a record gets created in the database, otherwise
-
# the existing record gets updated.
-
#
-
# With <tt>save!</tt> validations always run. If any of them fail
-
# ActiveRecord::RecordInvalid gets raised. See ActiveRecord::Validations
-
# for more information.
-
#
-
# There's a series of callbacks associated with <tt>save!</tt>. If any of
-
# the <tt>before_*</tt> callbacks return +false+ the action is cancelled
-
# and <tt>save!</tt> raises ActiveRecord::RecordNotSaved. See
-
# ActiveRecord::Callbacks for further details.
-
#
-
# Attributes marked as readonly are silently ignored if the record is
-
# being updated.
-
1
def save!(*)
-
17
create_or_update || raise(RecordNotSaved.new("Failed to save the record", self))
-
end
-
-
# Deletes the record in the database and freezes this instance to
-
# reflect that no changes should be made (since they can't be
-
# persisted). Returns the frozen instance.
-
#
-
# The row is simply removed with an SQL +DELETE+ statement on the
-
# record's primary key, and no callbacks are executed.
-
#
-
# To enforce the object's +before_destroy+ and +after_destroy+
-
# callbacks or any <tt>:dependent</tt> association
-
# options, use <tt>#destroy</tt>.
-
1
def delete
-
self.class.delete(id) if persisted?
-
@destroyed = true
-
freeze
-
end
-
-
# Deletes the record in the database and freezes this instance to reflect
-
# that no changes should be made (since they can't be persisted).
-
#
-
# There's a series of callbacks associated with <tt>destroy</tt>. If
-
# the <tt>before_destroy</tt> callback return +false+ the action is cancelled
-
# and <tt>destroy</tt> returns +false+. See
-
# ActiveRecord::Callbacks for further details.
-
1
def destroy
-
1
raise ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly?
-
1
destroy_associations
-
1
self.class.connection.add_transaction_record(self)
-
1
destroy_row if persisted?
-
1
@destroyed = true
-
1
freeze
-
end
-
-
# Deletes the record in the database and freezes this instance to reflect
-
# that no changes should be made (since they can't be persisted).
-
#
-
# There's a series of callbacks associated with <tt>destroy!</tt>. If
-
# the <tt>before_destroy</tt> callback return +false+ the action is cancelled
-
# and <tt>destroy!</tt> raises ActiveRecord::RecordNotDestroyed. See
-
# ActiveRecord::Callbacks for further details.
-
1
def destroy!
-
destroy || raise(RecordNotDestroyed.new("Failed to destroy the record", self))
-
end
-
-
# Returns an instance of the specified +klass+ with the attributes of the
-
# current record. This is mostly useful in relation to single-table
-
# inheritance structures where you want a subclass to appear as the
-
# superclass. This can be used along with record identification in
-
# Action Pack to allow, say, <tt>Client < Company</tt> to do something
-
# like render <tt>partial: @client.becomes(Company)</tt> to render that
-
# instance using the companies/company partial instead of clients/client.
-
#
-
# Note: The new instance will share a link to the same attributes as the original class.
-
# So any change to the attributes in either instance will affect the other.
-
1
def becomes(klass)
-
became = klass.new
-
became.instance_variable_set("@attributes", @attributes)
-
changed_attributes = @changed_attributes if defined?(@changed_attributes)
-
became.instance_variable_set("@changed_attributes", changed_attributes || {})
-
became.instance_variable_set("@new_record", new_record?)
-
became.instance_variable_set("@destroyed", destroyed?)
-
became.instance_variable_set("@errors", errors)
-
became
-
end
-
-
# Wrapper around +becomes+ that also changes the instance's sti column value.
-
# This is especially useful if you want to persist the changed class in your
-
# database.
-
#
-
# Note: The old instance's sti column value will be changed too, as both objects
-
# share the same set of attributes.
-
1
def becomes!(klass)
-
became = becomes(klass)
-
sti_type = nil
-
if !klass.descends_from_active_record?
-
sti_type = klass.sti_name
-
end
-
became.public_send("#{klass.inheritance_column}=", sti_type)
-
became
-
end
-
-
# Updates a single attribute and saves the record.
-
# This is especially useful for boolean flags on existing records. Also note that
-
#
-
# * Validation is skipped.
-
# * Callbacks are invoked.
-
# * updated_at/updated_on column is updated if that column is available.
-
# * Updates all the attributes that are dirty in this object.
-
#
-
# This method raises an +ActiveRecord::ActiveRecordError+ if the
-
# attribute is marked as readonly.
-
#
-
# See also +update_column+.
-
1
def update_attribute(name, value)
-
name = name.to_s
-
verify_readonly_attribute(name)
-
send("#{name}=", value)
-
save(validate: false)
-
end
-
-
# Updates the attributes of the model from the passed-in hash and saves the
-
# record, all wrapped in a transaction. If the object is invalid, the saving
-
# will fail and false will be returned.
-
1
def update(attributes)
-
# The following transaction covers any possible database side-effects of the
-
# attributes assignment. For example, setting the IDs of a child collection.
-
2
with_transaction_returning_status do
-
2
assign_attributes(attributes)
-
2
save
-
end
-
end
-
-
1
alias update_attributes update
-
-
# Updates its receiver just like +update+ but calls <tt>save!</tt> instead
-
# of +save+, so an exception is raised if the record is invalid.
-
1
def update!(attributes)
-
# The following transaction covers any possible database side-effects of the
-
# attributes assignment. For example, setting the IDs of a child collection.
-
with_transaction_returning_status do
-
assign_attributes(attributes)
-
save!
-
end
-
end
-
-
1
alias update_attributes! update!
-
-
# Equivalent to <code>update_columns(name => value)</code>.
-
1
def update_column(name, value)
-
update_columns(name => value)
-
end
-
-
# Updates the attributes directly in the database issuing an UPDATE SQL
-
# statement and sets them in the receiver:
-
#
-
# user.update_columns(last_request_at: Time.current)
-
#
-
# This is the fastest way to update attributes because it goes straight to
-
# the database, but take into account that in consequence the regular update
-
# procedures are totally bypassed. In particular:
-
#
-
# * Validations are skipped.
-
# * Callbacks are skipped.
-
# * +updated_at+/+updated_on+ are not updated.
-
#
-
# This method raises an +ActiveRecord::ActiveRecordError+ when called on new
-
# objects, or when at least one of the attributes is marked as readonly.
-
1
def update_columns(attributes)
-
raise ActiveRecordError, "cannot update a new record" if new_record?
-
raise ActiveRecordError, "cannot update a destroyed record" if destroyed?
-
-
attributes.each_key do |key|
-
verify_readonly_attribute(key.to_s)
-
end
-
-
updated_count = self.class.unscoped.where(self.class.primary_key => id).update_all(attributes)
-
-
attributes.each do |k, v|
-
raw_write_attribute(k, v)
-
end
-
-
updated_count == 1
-
end
-
-
# Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1).
-
# The increment is performed directly on the underlying attribute, no setter is invoked.
-
# Only makes sense for number-based attributes. Returns +self+.
-
1
def increment(attribute, by = 1)
-
self[attribute] ||= 0
-
self[attribute] += by
-
self
-
end
-
-
# Wrapper around +increment+ that saves the record. This method differs from
-
# its non-bang version in that it passes through the attribute setter.
-
# Saving is not subjected to validation checks. Returns +true+ if the
-
# record could be saved.
-
1
def increment!(attribute, by = 1)
-
increment(attribute, by).update_attribute(attribute, self[attribute])
-
end
-
-
# Initializes +attribute+ to zero if +nil+ and subtracts the value passed as +by+ (default is 1).
-
# The decrement is performed directly on the underlying attribute, no setter is invoked.
-
# Only makes sense for number-based attributes. Returns +self+.
-
1
def decrement(attribute, by = 1)
-
self[attribute] ||= 0
-
self[attribute] -= by
-
self
-
end
-
-
# Wrapper around +decrement+ that saves the record. This method differs from
-
# its non-bang version in that it passes through the attribute setter.
-
# Saving is not subjected to validation checks. Returns +true+ if the
-
# record could be saved.
-
1
def decrement!(attribute, by = 1)
-
decrement(attribute, by).update_attribute(attribute, self[attribute])
-
end
-
-
# Assigns to +attribute+ the boolean opposite of <tt>attribute?</tt>. So
-
# if the predicate returns +true+ the attribute will become +false+. This
-
# method toggles directly the underlying value without calling any setter.
-
# Returns +self+.
-
1
def toggle(attribute)
-
self[attribute] = !send("#{attribute}?")
-
self
-
end
-
-
# Wrapper around +toggle+ that saves the record. This method differs from
-
# its non-bang version in that it passes through the attribute setter.
-
# Saving is not subjected to validation checks. Returns +true+ if the
-
# record could be saved.
-
1
def toggle!(attribute)
-
toggle(attribute).update_attribute(attribute, self[attribute])
-
end
-
-
# Reloads the record from the database.
-
#
-
# This method finds record by its primary key (which could be assigned manually) and
-
# modifies the receiver in-place:
-
#
-
# account = Account.new
-
# # => #<Account id: nil, email: nil>
-
# account.id = 1
-
# account.reload
-
# # Account Load (1.2ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."id" = $1 LIMIT 1 [["id", 1]]
-
# # => #<Account id: 1, email: 'account@example.com'>
-
#
-
# Attributes are reloaded from the database, and caches busted, in
-
# particular the associations cache and the QueryCache.
-
#
-
# If the record no longer exists in the database <tt>ActiveRecord::RecordNotFound</tt>
-
# is raised. Otherwise, in addition to the in-place modification the method
-
# returns +self+ for convenience.
-
#
-
# The optional <tt>:lock</tt> flag option allows you to lock the reloaded record:
-
#
-
# reload(lock: true) # reload with pessimistic locking
-
#
-
# Reloading is commonly used in test suites to test something is actually
-
# written to the database, or when some action modifies the corresponding
-
# row in the database but not the object in memory:
-
#
-
# assert account.deposit!(25)
-
# assert_equal 25, account.credit # check it is updated in memory
-
# assert_equal 25, account.reload.credit # check it is also persisted
-
#
-
# Another common use case is optimistic locking handling:
-
#
-
# def with_optimistic_retry
-
# begin
-
# yield
-
# rescue ActiveRecord::StaleObjectError
-
# begin
-
# # Reload lock_version in particular.
-
# reload
-
# rescue ActiveRecord::RecordNotFound
-
# # If the record is gone there is nothing to do.
-
# else
-
# retry
-
# end
-
# end
-
# end
-
#
-
1
def reload(options = nil)
-
clear_aggregation_cache
-
clear_association_cache
-
self.class.connection.clear_query_cache
-
-
fresh_object =
-
if options && options[:lock]
-
self.class.unscoped { self.class.lock(options[:lock]).find(id) }
-
else
-
self.class.unscoped { self.class.find(id) }
-
end
-
-
@attributes = fresh_object.instance_variable_get('@attributes')
-
@new_record = false
-
self
-
end
-
-
# Saves the record with the updated_at/on attributes set to the current time.
-
# Please note that no validation is performed and only the +after_touch+,
-
# +after_commit+ and +after_rollback+ callbacks are executed.
-
#
-
# If attribute names are passed, they are updated along with updated_at/on
-
# attributes.
-
#
-
# product.touch # updates updated_at/on
-
# product.touch(:designed_at) # updates the designed_at attribute and updated_at/on
-
# product.touch(:started_at, :ended_at) # updates started_at, ended_at and updated_at/on attributes
-
#
-
# If used along with +belongs_to+ then +touch+ will invoke +touch+ method on
-
# associated object.
-
#
-
# class Brake < ActiveRecord::Base
-
# belongs_to :car, touch: true
-
# end
-
#
-
# class Car < ActiveRecord::Base
-
# belongs_to :corporation, touch: true
-
# end
-
#
-
# # triggers @brake.car.touch and @brake.car.corporation.touch
-
# @brake.touch
-
#
-
# Note that +touch+ must be used on a persisted object, or else an
-
# ActiveRecordError will be thrown. For example:
-
#
-
# ball = Ball.new
-
# ball.touch(:updated_at) # => raises ActiveRecordError
-
#
-
1
def touch(*names)
-
raise ActiveRecordError, "cannot touch on a new record object" unless persisted?
-
-
attributes = timestamp_attributes_for_update_in_model
-
attributes.concat(names)
-
-
unless attributes.empty?
-
current_time = current_time_from_proper_timezone
-
changes = {}
-
-
attributes.each do |column|
-
column = column.to_s
-
changes[column] = write_attribute(column, current_time)
-
end
-
-
changes[self.class.locking_column] = increment_lock if locking_enabled?
-
-
clear_attribute_changes(changes.keys)
-
primary_key = self.class.primary_key
-
self.class.unscoped.where(primary_key => self[primary_key]).update_all(changes) == 1
-
else
-
true
-
end
-
end
-
-
1
private
-
-
# A hook to be overridden by association modules.
-
1
def destroy_associations
-
end
-
-
1
def destroy_row
-
1
relation_for_destroy.delete_all
-
end
-
-
1
def relation_for_destroy
-
1
pk = self.class.primary_key
-
1
column = self.class.columns_hash[pk]
-
1
substitute = self.class.connection.substitute_at(column)
-
-
1
relation = self.class.unscoped.where(
-
self.class.arel_table[pk].eq(substitute))
-
-
1
relation.bind_values = [[column, id]]
-
1
relation
-
end
-
-
1
def create_or_update
-
31
raise ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly?
-
31
result = new_record? ? _create_record : _update_record
-
31
result != false
-
end
-
-
# Updates the associated record with values matching those of the instance attributes.
-
# Returns the number of affected rows.
-
1
def _update_record(attribute_names = self.attribute_names)
-
3
attributes_values = arel_attributes_with_values_for_update(attribute_names)
-
3
if attributes_values.empty?
-
1
0
-
else
-
2
self.class.unscoped._update_record attributes_values, id, id_was
-
end
-
end
-
-
# Creates a record with values matching those of the instance attributes
-
# and returns its id.
-
1
def _create_record(attribute_names = self.attribute_names)
-
28
attributes_values = arel_attributes_with_values_for_create(attribute_names)
-
-
28
new_id = self.class.unscoped.insert attributes_values
-
28
self.id ||= new_id if self.class.primary_key
-
-
28
@new_record = false
-
28
id
-
end
-
-
1
def verify_readonly_attribute(name)
-
raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Querying
-
1
delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, to: :all
-
1
delegate :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!, :forty_two, :forty_two!, to: :all
-
1
delegate :first_or_create, :first_or_create!, :first_or_initialize, to: :all
-
1
delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, to: :all
-
1
delegate :find_by, :find_by!, to: :all
-
1
delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, to: :all
-
1
delegate :find_each, :find_in_batches, to: :all
-
1
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins,
-
:where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly,
-
:having, :create_with, :uniq, :distinct, :references, :none, :unscope, to: :all
-
1
delegate :count, :average, :minimum, :maximum, :sum, :calculate, to: :all
-
1
delegate :pluck, :ids, to: :all
-
-
# Executes a custom SQL query against your database and returns all the results. The results will
-
# be returned as an array with columns requested encapsulated as attributes of the model you call
-
# this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in
-
# a +Product+ object with the attributes you specified in the SQL query.
-
#
-
# If you call a complicated SQL query which spans multiple tables the columns specified by the
-
# SELECT will be attributes of the model, whether or not they are columns of the corresponding
-
# table.
-
#
-
# The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be
-
# no database agnostic conversions performed. This should be a last resort because using, for example,
-
# MySQL specific terms will lock you to using that particular database engine or require you to
-
# change your call if you switch engines.
-
#
-
# # A simple SQL query spanning multiple tables
-
# Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id"
-
# # => [#<Post:0x36bff9c @attributes={"title"=>"Ruby Meetup", "first_name"=>"Quentin"}>, ...]
-
#
-
# You can use the same string replacement techniques as you can with <tt>ActiveRecord::QueryMethods#where</tt>:
-
#
-
# Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
-
# Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }]
-
1
def find_by_sql(sql, binds = [])
-
50
result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds)
-
50
column_types = result_set.column_types.dup
-
745
columns_hash.each_key { |k| column_types.delete k }
-
50
message_bus = ActiveSupport::Notifications.instrumenter
-
-
50
payload = {
-
record_count: result_set.length,
-
class_name: name
-
}
-
-
50
message_bus.instrument('instantiation.active_record', payload) do
-
77
result_set.map { |record| instantiate(record, column_types) }
-
end
-
end
-
-
# Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
-
# The use of this method should be restricted to complicated SQL queries that can't be executed
-
# using the ActiveRecord::Calculations class methods. Look into those before using this.
-
#
-
# ==== Parameters
-
#
-
# * +sql+ - An SQL statement which should return a count query from the database, see the example below.
-
#
-
# Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
-
1
def count_by_sql(sql)
-
sql = sanitize_conditions(sql)
-
connection.select_value(sql, "#{name} Count").to_i
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ReadonlyAttributes
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
class_attribute :_attr_readonly, instance_accessor: false
-
1
self._attr_readonly = []
-
end
-
-
1
module ClassMethods
-
# Attributes listed as readonly will be used to create a new record but update operations will
-
# ignore these fields.
-
1
def attr_readonly(*attributes)
-
self._attr_readonly = Set.new(attributes.map { |a| a.to_s }) + (self._attr_readonly || [])
-
end
-
-
# Returns an array of all the attributes that have been specified as readonly.
-
1
def readonly_attributes
-
10
self._attr_readonly
-
end
-
end
-
end
-
end
-
1
require 'thread'
-
1
require 'active_support/core_ext/string/filters'
-
-
1
module ActiveRecord
-
# = Active Record Reflection
-
1
module Reflection # :nodoc:
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
class_attribute :_reflections
-
1
class_attribute :aggregate_reflections
-
1
self._reflections = {}
-
1
self.aggregate_reflections = {}
-
end
-
-
1
def self.create(macro, name, scope, options, ar)
-
2
klass = case macro
-
when :composed_of
-
AggregateReflection
-
when :has_many
-
1
HasManyReflection
-
when :has_one
-
HasOneReflection
-
when :belongs_to
-
1
BelongsToReflection
-
else
-
raise "Unsupported Macro: #{macro}"
-
end
-
-
2
reflection = klass.new(name, scope, options, ar)
-
2
options[:through] ? ThroughReflection.new(reflection) : reflection
-
end
-
-
1
def self.add_reflection(ar, name, reflection)
-
2
ar.clear_reflections_cache
-
2
ar._reflections = ar._reflections.merge(name.to_s => reflection)
-
end
-
-
1
def self.add_aggregate_reflection(ar, name, reflection)
-
ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_s => reflection)
-
end
-
-
# \Reflection enables interrogating of Active Record classes and objects
-
# about their associations and aggregations. This information can,
-
# for example, be used in a form builder that takes an Active Record object
-
# and creates input fields for all of the attributes depending on their type
-
# and displays the associations to other objects.
-
#
-
# MacroReflection class has info for AggregateReflection and AssociationReflection
-
# classes.
-
1
module ClassMethods
-
# Returns an array of AggregateReflection objects for all the aggregations in the class.
-
1
def reflect_on_all_aggregations
-
8
aggregate_reflections.values
-
end
-
-
# Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
-
#
-
# Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
-
#
-
1
def reflect_on_aggregation(aggregation)
-
192
aggregate_reflections[aggregation.to_s]
-
end
-
-
# Returns a Hash of name of the reflection as the key and a AssociationReflection as the value.
-
#
-
# Account.reflections # => {"balance" => AggregateReflection}
-
#
-
# @api public
-
1
def reflections
-
@__reflections ||= begin
-
1
ref = {}
-
-
1
_reflections.each do |name, reflection|
-
1
parent_name, parent_reflection = reflection.parent_reflection
-
-
1
if parent_name
-
ref[parent_name] = parent_reflection
-
else
-
1
ref[name] = reflection
-
end
-
end
-
-
1
ref
-
1
end
-
end
-
-
# Returns an array of AssociationReflection objects for all the
-
# associations in the class. If you only want to reflect on a certain
-
# association type, pass in the symbol (<tt>:has_many</tt>, <tt>:has_one</tt>,
-
# <tt>:belongs_to</tt>) as the first parameter.
-
#
-
# Example:
-
#
-
# Account.reflect_on_all_associations # returns an array of all associations
-
# Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
-
#
-
# @api public
-
1
def reflect_on_all_associations(macro = nil)
-
association_reflections = reflections.values
-
macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
-
end
-
-
# Returns the AssociationReflection object for the +association+ (use the symbol).
-
#
-
# Account.reflect_on_association(:owner) # returns the owner AssociationReflection
-
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
-
#
-
# @api public
-
1
def reflect_on_association(association)
-
1
reflections[association.to_s]
-
end
-
-
# @api private
-
1
def _reflect_on_association(association) #:nodoc:
-
317
_reflections[association.to_s]
-
end
-
-
# Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
-
#
-
# @api public
-
1
def reflect_on_all_autosave_associations
-
reflections.values.select { |reflection| reflection.options[:autosave] }
-
end
-
-
1
def clear_reflections_cache #:nodoc:
-
2
@__reflections = nil
-
end
-
end
-
-
# Holds all the methods that are shared between MacroReflection, AssociationReflection
-
# and ThroughReflection
-
1
class AbstractReflection # :nodoc:
-
1
def table_name
-
klass.table_name
-
end
-
-
# Returns a new, unsaved instance of the associated class. +attributes+ will
-
# be passed to the class's constructor.
-
1
def build_association(attributes, &block)
-
klass.new(attributes, &block)
-
end
-
-
1
def quoted_table_name
-
klass.quoted_table_name
-
end
-
-
1
def primary_key_type
-
klass.type_for_attribute(klass.primary_key)
-
end
-
-
# Returns the class name for the macro.
-
#
-
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>'Money'</tt>
-
# <tt>has_many :clients</tt> returns <tt>'Client'</tt>
-
1
def class_name
-
1
@class_name ||= (options[:class_name] || derive_class_name).to_s
-
end
-
-
1
JoinKeys = Struct.new(:key, :foreign_key) # :nodoc:
-
-
1
def join_keys(assoc_klass)
-
JoinKeys.new(foreign_key, active_record_primary_key)
-
end
-
-
1
def source_macro
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
ActiveRecord::Base.source_macro is deprecated and will be removed
-
without replacement.
-
MSG
-
-
macro
-
end
-
-
1
def inverse_of
-
return unless inverse_name
-
-
@inverse_of ||= klass._reflect_on_association inverse_name
-
end
-
-
1
def check_validity_of_inverse!
-
unless polymorphic?
-
if has_inverse? && inverse_of.nil?
-
raise InverseOfAssociationNotFoundError.new(self)
-
end
-
end
-
end
-
end
-
# Base class for AggregateReflection and AssociationReflection. Objects of
-
# AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
-
#
-
# MacroReflection
-
# AssociationReflection
-
# AggregateReflection
-
# HasManyReflection
-
# HasOneReflection
-
# BelongsToReflection
-
# ThroughReflection
-
1
class MacroReflection < AbstractReflection
-
# Returns the name of the macro.
-
#
-
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>:balance</tt>
-
# <tt>has_many :clients</tt> returns <tt>:clients</tt>
-
1
attr_reader :name
-
-
1
attr_reader :scope
-
-
# Returns the hash of options used for the macro.
-
#
-
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>{ class_name: "Money" }</tt>
-
# <tt>has_many :clients</tt> returns <tt>{}</tt>
-
1
attr_reader :options
-
-
1
attr_reader :active_record
-
-
1
attr_reader :plural_name # :nodoc:
-
-
1
def initialize(name, scope, options, active_record)
-
2
@name = name
-
2
@scope = scope
-
2
@options = options
-
2
@active_record = active_record
-
2
@klass = options[:anonymous_class]
-
2
@plural_name = active_record.pluralize_table_names ?
-
name.to_s.pluralize : name.to_s
-
end
-
-
1
def autosave=(autosave)
-
@automatic_inverse_of = false
-
@options[:autosave] = autosave
-
_, parent_reflection = self.parent_reflection
-
if parent_reflection
-
parent_reflection.autosave = autosave
-
end
-
end
-
-
# Returns the class for the macro.
-
#
-
# <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class
-
# <tt>has_many :clients</tt> returns the Client class
-
1
def klass
-
@klass ||= compute_class(class_name)
-
end
-
-
1
def compute_class(name)
-
name.constantize
-
end
-
-
# Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
-
# and +other_aggregation+ has an options hash assigned to it.
-
1
def ==(other_aggregation)
-
super ||
-
other_aggregation.kind_of?(self.class) &&
-
name == other_aggregation.name &&
-
!other_aggregation.options.nil? &&
-
active_record == other_aggregation.active_record
-
end
-
-
1
private
-
1
def derive_class_name
-
name.to_s.camelize
-
end
-
end
-
-
-
# Holds all the meta-data about an aggregation as it was specified in the
-
# Active Record class.
-
1
class AggregateReflection < MacroReflection #:nodoc:
-
1
def mapping
-
mapping = options[:mapping] || [name, name]
-
mapping.first.is_a?(Array) ? mapping : [mapping]
-
end
-
end
-
-
# Holds all the meta-data about an association as it was specified in the
-
# Active Record class.
-
1
class AssociationReflection < MacroReflection #:nodoc:
-
# Returns the target association's class.
-
#
-
# class Author < ActiveRecord::Base
-
# has_many :books
-
# end
-
#
-
# Author.reflect_on_association(:books).klass
-
# # => Book
-
#
-
# <b>Note:</b> Do not call +klass.new+ or +klass.create+ to instantiate
-
# a new association object. Use +build_association+ or +create_association+
-
# instead. This allows plugins to hook into association object creation.
-
1
def klass
-
1
@klass ||= compute_class(class_name)
-
end
-
-
1
def compute_class(name)
-
1
active_record.send(:compute_type, name)
-
end
-
-
1
attr_reader :type, :foreign_type
-
1
attr_accessor :parent_reflection # [:name, Reflection]
-
-
1
def initialize(name, scope, options, active_record)
-
2
super
-
2
@automatic_inverse_of = nil
-
2
@type = options[:as] && (options[:foreign_type] || "#{options[:as]}_type")
-
2
@foreign_type = options[:foreign_type] || "#{name}_type"
-
2
@constructable = calculate_constructable(macro, options)
-
2
@association_scope_cache = {}
-
2
@scope_lock = Mutex.new
-
end
-
-
1
def association_scope_cache(conn, owner)
-
key = conn.prepared_statements
-
if polymorphic?
-
key = [key, owner._read_attribute(@foreign_type)]
-
end
-
@association_scope_cache[key] ||= @scope_lock.synchronize {
-
@association_scope_cache[key] ||= yield
-
}
-
end
-
-
1
def constructable? # :nodoc:
-
1
@constructable
-
end
-
-
1
def join_table
-
@join_table ||= options[:join_table] || derive_join_table
-
end
-
-
1
def foreign_key
-
1
@foreign_key ||= options[:foreign_key] || derive_foreign_key
-
end
-
-
1
def association_foreign_key
-
@association_foreign_key ||= options[:association_foreign_key] || class_name.foreign_key
-
end
-
-
# klass option is necessary to support loading polymorphic associations
-
1
def association_primary_key(klass = nil)
-
options[:primary_key] || primary_key(klass || self.klass)
-
end
-
-
1
def active_record_primary_key
-
@active_record_primary_key ||= options[:primary_key] || primary_key(active_record)
-
end
-
-
1
def counter_cache_column
-
1
if options[:counter_cache] == true
-
"#{active_record.name.demodulize.underscore.pluralize}_count"
-
1
elsif options[:counter_cache]
-
options[:counter_cache].to_s
-
end
-
end
-
-
1
def check_validity!
-
check_validity_of_inverse!
-
end
-
-
1
def check_preloadable!
-
return unless scope
-
-
if scope.arity > 0
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
The association scope '#{name}' is instance dependent (the scope
-
block takes an argument). Preloading happens before the individual
-
instances are created. This means that there is no instance being
-
passed to the association scope. This will most likely result in
-
broken or incorrect behavior. Joining, Preloading and eager loading
-
of these associations is deprecated and will be removed in the future.
-
MSG
-
end
-
end
-
1
alias :check_eager_loadable! :check_preloadable!
-
-
1
def join_id_for(owner) # :nodoc:
-
owner[active_record_primary_key]
-
end
-
-
1
def through_reflection
-
nil
-
end
-
-
1
def source_reflection
-
self
-
end
-
-
# A chain of reflections from this one back to the owner. For more see the explanation in
-
# ThroughReflection.
-
1
def chain
-
[self]
-
end
-
-
1
def nested?
-
false
-
end
-
-
# An array of arrays of scopes. Each item in the outside array corresponds to a reflection
-
# in the #chain.
-
1
def scope_chain
-
scope ? [[scope]] : [[]]
-
end
-
-
1
def has_inverse?
-
inverse_name
-
end
-
-
1
def polymorphic_inverse_of(associated_class)
-
if has_inverse?
-
if inverse_relationship = associated_class._reflect_on_association(options[:inverse_of])
-
inverse_relationship
-
else
-
raise InverseOfAssociationNotFoundError.new(self, associated_class)
-
end
-
end
-
end
-
-
# Returns the macro type.
-
#
-
# <tt>has_many :clients</tt> returns <tt>:has_many</tt>
-
1
def macro; raise NotImplementedError; end
-
-
# Returns whether or not this association reflection is for a collection
-
# association. Returns +true+ if the +macro+ is either +has_many+ or
-
# +has_and_belongs_to_many+, +false+ otherwise.
-
1
def collection?
-
3
false
-
end
-
-
# Returns whether or not the association should be validated as part of
-
# the parent's validation.
-
#
-
# Unless you explicitly disable validation with
-
# <tt>validate: false</tt>, validation will take place when:
-
#
-
# * you explicitly enable validation; <tt>validate: true</tt>
-
# * you use autosave; <tt>autosave: true</tt>
-
# * the association is a +has_many+ association
-
1
def validate?
-
2
!options[:validate].nil? ? options[:validate] : (options[:autosave] == true || collection?)
-
end
-
-
# Returns +true+ if +self+ is a +belongs_to+ reflection.
-
23
def belongs_to?; false; end
-
-
# Returns +true+ if +self+ is a +has_one+ reflection.
-
2
def has_one?; false; end
-
-
1
def association_class
-
case macro
-
when :belongs_to
-
if polymorphic?
-
Associations::BelongsToPolymorphicAssociation
-
else
-
Associations::BelongsToAssociation
-
end
-
when :has_many
-
if options[:through]
-
Associations::HasManyThroughAssociation
-
else
-
Associations::HasManyAssociation
-
end
-
when :has_one
-
if options[:through]
-
Associations::HasOneThroughAssociation
-
else
-
Associations::HasOneAssociation
-
end
-
end
-
end
-
-
1
def polymorphic?
-
1
options[:polymorphic]
-
end
-
-
1
VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to]
-
1
INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :polymorphic, :foreign_key]
-
-
1
protected
-
-
1
def actual_source_reflection # FIXME: this is a horrible name
-
self
-
end
-
-
1
private
-
-
1
def calculate_constructable(macro, options)
-
2
case macro
-
when :belongs_to
-
1
!polymorphic?
-
when :has_one
-
!options[:through]
-
else
-
1
true
-
end
-
end
-
-
# Attempts to find the inverse association name automatically.
-
# If it cannot find a suitable inverse association name, it returns
-
# nil.
-
1
def inverse_name
-
options.fetch(:inverse_of) do
-
if @automatic_inverse_of == false
-
nil
-
else
-
@automatic_inverse_of ||= automatic_inverse_of
-
end
-
end
-
end
-
-
# returns either nil or the inverse association name that it finds.
-
1
def automatic_inverse_of
-
if can_find_inverse_of_automatically?(self)
-
inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym
-
-
begin
-
reflection = klass._reflect_on_association(inverse_name)
-
rescue NameError
-
# Give up: we couldn't compute the klass type so we won't be able
-
# to find any associations either.
-
reflection = false
-
end
-
-
if valid_inverse_reflection?(reflection)
-
return inverse_name
-
end
-
end
-
-
false
-
end
-
-
# Checks if the inverse reflection that is returned from the
-
# +automatic_inverse_of+ method is a valid reflection. We must
-
# make sure that the reflection's active_record name matches up
-
# with the current reflection's klass name.
-
#
-
# Note: klass will always be valid because when there's a NameError
-
# from calling +klass+, +reflection+ will already be set to false.
-
1
def valid_inverse_reflection?(reflection)
-
reflection &&
-
klass.name == reflection.active_record.name &&
-
can_find_inverse_of_automatically?(reflection)
-
end
-
-
# Checks to see if the reflection doesn't have any options that prevent
-
# us from being able to guess the inverse automatically. First, the
-
# <tt>inverse_of</tt> option cannot be set to false. Second, we must
-
# have <tt>has_many</tt>, <tt>has_one</tt>, <tt>belongs_to</tt> associations.
-
# Third, we must not have options such as <tt>:polymorphic</tt> or
-
# <tt>:foreign_key</tt> which prevent us from correctly guessing the
-
# inverse association.
-
#
-
# Anything with a scope can additionally ruin our attempt at finding an
-
# inverse, so we exclude reflections with scopes.
-
1
def can_find_inverse_of_automatically?(reflection)
-
reflection.options[:inverse_of] != false &&
-
VALID_AUTOMATIC_INVERSE_MACROS.include?(reflection.macro) &&
-
!INVALID_AUTOMATIC_INVERSE_OPTIONS.any? { |opt| reflection.options[opt] } &&
-
!reflection.scope
-
end
-
-
1
def derive_class_name
-
1
class_name = name.to_s
-
1
class_name = class_name.singularize if collection?
-
1
class_name.camelize
-
end
-
-
1
def derive_foreign_key
-
1
if belongs_to?
-
1
"#{name}_id"
-
elsif options[:as]
-
"#{options[:as]}_id"
-
else
-
active_record.name.foreign_key
-
end
-
end
-
-
1
def derive_join_table
-
ModelSchema.derive_join_table_name active_record.table_name, klass.table_name
-
end
-
-
1
def primary_key(klass)
-
klass.primary_key || raise(UnknownPrimaryKey.new(klass))
-
end
-
end
-
-
1
class HasManyReflection < AssociationReflection # :nodoc:
-
1
def initialize(name, scope, options, active_record)
-
1
super(name, scope, options, active_record)
-
end
-
-
2
def macro; :has_many; end
-
-
4
def collection?; true; end
-
end
-
-
1
class HasOneReflection < AssociationReflection # :nodoc:
-
1
def initialize(name, scope, options, active_record)
-
super(name, scope, options, active_record)
-
end
-
-
1
def macro; :has_one; end
-
-
1
def has_one?; true; end
-
end
-
-
1
class BelongsToReflection < AssociationReflection # :nodoc:
-
1
def initialize(name, scope, options, active_record)
-
1
super(name, scope, options, active_record)
-
end
-
-
3
def macro; :belongs_to; end
-
-
3
def belongs_to?; true; end
-
-
1
def join_keys(assoc_klass)
-
key = polymorphic? ? association_primary_key(assoc_klass) : association_primary_key
-
JoinKeys.new(key, foreign_key)
-
end
-
-
1
def join_id_for(owner) # :nodoc:
-
owner[foreign_key]
-
end
-
end
-
-
1
class HasAndBelongsToManyReflection < AssociationReflection # :nodoc:
-
1
def initialize(name, scope, options, active_record)
-
super
-
end
-
-
1
def macro; :has_and_belongs_to_many; end
-
-
1
def collection?
-
true
-
end
-
end
-
-
# Holds all the meta-data about a :through association as it was specified
-
# in the Active Record class.
-
1
class ThroughReflection < AbstractReflection #:nodoc:
-
1
attr_reader :delegate_reflection
-
1
delegate :foreign_key, :foreign_type, :association_foreign_key,
-
:active_record_primary_key, :type, :to => :source_reflection
-
-
1
def initialize(delegate_reflection)
-
@delegate_reflection = delegate_reflection
-
@klass = delegate_reflection.options[:anonymous_class]
-
@source_reflection_name = delegate_reflection.options[:source]
-
end
-
-
1
def klass
-
@klass ||= delegate_reflection.compute_class(class_name)
-
end
-
-
# Returns the source of the through reflection. It checks both a singularized
-
# and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :taggings
-
# has_many :tags, through: :taggings
-
# end
-
#
-
# class Tagging < ActiveRecord::Base
-
# belongs_to :post
-
# belongs_to :tag
-
# end
-
#
-
# tags_reflection = Post.reflect_on_association(:tags)
-
# tags_reflection.source_reflection
-
# # => <ActiveRecord::Reflection::BelongsToReflection: @name=:tag, @active_record=Tagging, @plural_name="tags">
-
#
-
1
def source_reflection
-
through_reflection.klass._reflect_on_association(source_reflection_name)
-
end
-
-
# Returns the AssociationReflection object specified in the <tt>:through</tt> option
-
# of a HasManyThrough or HasOneThrough association.
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :taggings
-
# has_many :tags, through: :taggings
-
# end
-
#
-
# tags_reflection = Post.reflect_on_association(:tags)
-
# tags_reflection.through_reflection
-
# # => <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @active_record=Post, @plural_name="taggings">
-
#
-
1
def through_reflection
-
active_record._reflect_on_association(options[:through])
-
end
-
-
# Returns an array of reflections which are involved in this association. Each item in the
-
# array corresponds to a table which will be part of the query for this association.
-
#
-
# The chain is built by recursively calling #chain on the source reflection and the through
-
# reflection. The base case for the recursion is a normal association, which just returns
-
# [self] as its #chain.
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :taggings
-
# has_many :tags, through: :taggings
-
# end
-
#
-
# tags_reflection = Post.reflect_on_association(:tags)
-
# tags_reflection.chain
-
# # => [<ActiveRecord::Reflection::ThroughReflection: @delegate_reflection=#<ActiveRecord::Reflection::HasManyReflection: @name=:tags...>,
-
# <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @options={}, @active_record=Post>]
-
#
-
1
def chain
-
@chain ||= begin
-
a = source_reflection.chain
-
b = through_reflection.chain
-
chain = a + b
-
chain[0] = self # Use self so we don't lose the information from :source_type
-
chain
-
end
-
end
-
-
# Consider the following example:
-
#
-
# class Person
-
# has_many :articles
-
# has_many :comment_tags, through: :articles
-
# end
-
#
-
# class Article
-
# has_many :comments
-
# has_many :comment_tags, through: :comments, source: :tags
-
# end
-
#
-
# class Comment
-
# has_many :tags
-
# end
-
#
-
# There may be scopes on Person.comment_tags, Article.comment_tags and/or Comment.tags,
-
# but only Comment.tags will be represented in the #chain. So this method creates an array
-
# of scopes corresponding to the chain.
-
1
def scope_chain
-
@scope_chain ||= begin
-
scope_chain = source_reflection.scope_chain.map(&:dup)
-
-
# Add to it the scope from this reflection (if any)
-
scope_chain.first << scope if scope
-
-
through_scope_chain = through_reflection.scope_chain.map(&:dup)
-
-
if options[:source_type]
-
type = foreign_type
-
source_type = options[:source_type]
-
through_scope_chain.first << lambda { |object|
-
where(type => source_type)
-
}
-
end
-
-
# Recursively fill out the rest of the array from the through reflection
-
scope_chain + through_scope_chain
-
end
-
end
-
-
1
def join_keys(assoc_klass)
-
source_reflection.join_keys(assoc_klass)
-
end
-
-
# The macro used by the source association
-
1
def source_macro
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
ActiveRecord::Base.source_macro is deprecated and will be removed
-
without replacement.
-
MSG
-
-
source_reflection.source_macro
-
end
-
-
# A through association is nested if there would be more than one join table
-
1
def nested?
-
chain.length > 2
-
end
-
-
# We want to use the klass from this reflection, rather than just delegate straight to
-
# the source_reflection, because the source_reflection may be polymorphic. We still
-
# need to respect the source_reflection's :primary_key option, though.
-
1
def association_primary_key(klass = nil)
-
# Get the "actual" source reflection if the immediate source reflection has a
-
# source reflection itself
-
actual_source_reflection.options[:primary_key] || primary_key(klass || self.klass)
-
end
-
-
# Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form.
-
#
-
# class Post < ActiveRecord::Base
-
# has_many :taggings
-
# has_many :tags, through: :taggings
-
# end
-
#
-
# tags_reflection = Post.reflect_on_association(:tags)
-
# tags_reflection.source_reflection_names
-
# # => [:tag, :tags]
-
#
-
1
def source_reflection_names
-
options[:source] ? [options[:source]] : [name.to_s.singularize, name].uniq
-
end
-
-
1
def source_reflection_name # :nodoc:
-
return @source_reflection_name if @source_reflection_name
-
-
names = [name.to_s.singularize, name].collect { |n| n.to_sym }.uniq
-
names = names.find_all { |n|
-
through_reflection.klass._reflect_on_association(n)
-
}
-
-
if names.length > 1
-
example_options = options.dup
-
example_options[:source] = source_reflection_names.first
-
ActiveSupport::Deprecation.warn \
-
"Ambiguous source reflection for through association. Please " \
-
"specify a :source directive on your declaration like:\n" \
-
"\n" \
-
" class #{active_record.name} < ActiveRecord::Base\n" \
-
" #{macro} :#{name}, #{example_options}\n" \
-
" end"
-
end
-
-
@source_reflection_name = names.first
-
end
-
-
1
def source_options
-
source_reflection.options
-
end
-
-
1
def through_options
-
through_reflection.options
-
end
-
-
1
def join_id_for(owner) # :nodoc:
-
source_reflection.join_id_for(owner)
-
end
-
-
1
def check_validity!
-
if through_reflection.nil?
-
raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
-
end
-
-
if through_reflection.polymorphic?
-
if has_one?
-
raise HasOneAssociationPolymorphicThroughError.new(active_record.name, self)
-
else
-
raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self)
-
end
-
end
-
-
if source_reflection.nil?
-
raise HasManyThroughSourceAssociationNotFoundError.new(self)
-
end
-
-
if options[:source_type] && !source_reflection.polymorphic?
-
raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
-
end
-
-
if source_reflection.polymorphic? && options[:source_type].nil?
-
raise HasManyThroughAssociationPolymorphicSourceError.new(active_record.name, self, source_reflection)
-
end
-
-
if has_one? && through_reflection.collection?
-
raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection)
-
end
-
-
check_validity_of_inverse!
-
end
-
-
1
protected
-
-
1
def actual_source_reflection # FIXME: this is a horrible name
-
source_reflection.send(:actual_source_reflection)
-
end
-
-
1
def primary_key(klass)
-
klass.primary_key || raise(UnknownPrimaryKey.new(klass))
-
end
-
-
1
def inverse_name; delegate_reflection.send(:inverse_name); end
-
-
1
private
-
1
def derive_class_name
-
# get the class_name of the belongs_to association of the through reflection
-
options[:source_type] || source_reflection.class_name
-
end
-
-
1
delegate_methods = AssociationReflection.public_instance_methods -
-
public_instance_methods
-
-
1
delegate(*delegate_methods, to: :delegate_reflection)
-
-
end
-
end
-
end
-
# -*- coding: utf-8 -*-
-
1
require 'arel/collectors/bind'
-
-
1
module ActiveRecord
-
# = Active Record Relation
-
1
class Relation
-
1
MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
-
:order, :joins, :where, :having, :bind, :references,
-
:extending, :unscope]
-
-
1
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering,
-
:reverse_order, :distinct, :create_with, :uniq]
-
1
INVALID_METHODS_FOR_DELETE_ALL = [:limit, :distinct, :offset, :group, :having]
-
-
1
VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS
-
-
1
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
-
-
1
attr_reader :table, :klass, :loaded
-
1
alias :model :klass
-
1
alias :loaded? :loaded
-
-
1
def initialize(klass, table, values = {})
-
258
@klass = klass
-
258
@table = table
-
258
@values = values
-
258
@offsets = {}
-
258
@loaded = false
-
end
-
-
1
def initialize_copy(other)
-
# This method is a hot spot, so for now, use Hash[] to dup the hash.
-
# https://bugs.ruby-lang.org/issues/7166
-
217
@values = Hash[@values]
-
217
@values[:bind] = @values[:bind].dup if @values.key? :bind
-
217
reset
-
end
-
-
1
def insert(values) # :nodoc:
-
28
primary_key_value = nil
-
-
28
if primary_key && Hash === values
-
28
primary_key_value = values[values.keys.find { |k|
-
345
k.name == primary_key
-
}]
-
-
28
if !primary_key_value && connection.prefetch_primary_key?(klass.table_name)
-
primary_key_value = connection.next_sequence_value(klass.sequence_name)
-
values[klass.arel_table[klass.primary_key]] = primary_key_value
-
end
-
end
-
-
28
im = arel.create_insert
-
28
im.into @table
-
-
28
substitutes, binds = substitute_values values
-
-
28
if values.empty? # empty insert
-
im.values = Arel.sql(connection.empty_insert_statement_value)
-
else
-
28
im.insert substitutes
-
end
-
-
28
@klass.connection.insert(
-
im,
-
'SQL',
-
primary_key,
-
primary_key_value,
-
nil,
-
binds)
-
end
-
-
1
def _update_record(values, id, id_was) # :nodoc:
-
2
substitutes, binds = substitute_values values
-
-
2
scope = @klass.unscoped
-
-
2
if @klass.finder_needs_type_condition?
-
scope.unscope!(where: @klass.inheritance_column)
-
end
-
-
2
relation = scope.where(@klass.primary_key => (id_was || id))
-
2
bvs = binds + relation.bind_values
-
2
um = relation
-
.arel
-
.compile_update(substitutes, @klass.primary_key)
-
-
2
@klass.connection.update(
-
um,
-
'SQL',
-
bvs,
-
)
-
end
-
-
1
def substitute_values(values) # :nodoc:
-
30
binds = values.map do |arel_attr, value|
-
355
[@klass.columns_hash[arel_attr.name], value]
-
end
-
-
30
substitutes = values.each_with_index.map do |(arel_attr, _), i|
-
355
[arel_attr, @klass.connection.substitute_at(binds[i][0])]
-
end
-
-
30
[substitutes, binds]
-
end
-
-
# Initializes new record from relation while maintaining the current
-
# scope.
-
#
-
# Expects arguments in the same format as +Base.new+.
-
#
-
# users = User.where(name: 'DHH')
-
# user = users.new # => #<User id: nil, name: "DHH", created_at: nil, updated_at: nil>
-
#
-
# You can also pass a block to new with the new record as argument:
-
#
-
# user = users.new { |user| user.name = 'Oscar' }
-
# user.name # => Oscar
-
1
def new(*args, &block)
-
scoping { @klass.new(*args, &block) }
-
end
-
-
1
alias build new
-
-
# Tries to create a new record with the same scoped attributes
-
# defined in the relation. Returns the initialized object if validation fails.
-
#
-
# Expects arguments in the same format as +Base.create+.
-
#
-
# ==== Examples
-
# users = User.where(name: 'Oscar')
-
# users.create # #<User id: 3, name: "oscar", ...>
-
#
-
# users.create(name: 'fxn')
-
# users.create # #<User id: 4, name: "fxn", ...>
-
#
-
# users.create { |user| user.name = 'tenderlove' }
-
# # #<User id: 5, name: "tenderlove", ...>
-
#
-
# users.create(name: nil) # validation on name
-
# # #<User id: nil, name: nil, ...>
-
1
def create(*args, &block)
-
scoping { @klass.create(*args, &block) }
-
end
-
-
# Similar to #create, but calls +create!+ on the base class. Raises
-
# an exception if a validation error occurs.
-
#
-
# Expects arguments in the same format as <tt>Base.create!</tt>.
-
1
def create!(*args, &block)
-
scoping { @klass.create!(*args, &block) }
-
end
-
-
1
def first_or_create(attributes = nil, &block) # :nodoc:
-
first || create(attributes, &block)
-
end
-
-
1
def first_or_create!(attributes = nil, &block) # :nodoc:
-
first || create!(attributes, &block)
-
end
-
-
1
def first_or_initialize(attributes = nil, &block) # :nodoc:
-
first || new(attributes, &block)
-
end
-
-
# Finds the first record with the given attributes, or creates a record
-
# with the attributes if one is not found:
-
#
-
# # Find the first user named "Penélope" or create a new one.
-
# User.find_or_create_by(first_name: 'Penélope')
-
# # => #<User id: 1, first_name: "Penélope", last_name: nil>
-
#
-
# # Find the first user named "Penélope" or create a new one.
-
# # We already have one so the existing record will be returned.
-
# User.find_or_create_by(first_name: 'Penélope')
-
# # => #<User id: 1, first_name: "Penélope", last_name: nil>
-
#
-
# # Find the first user named "Scarlett" or create a new one with
-
# # a particular last name.
-
# User.create_with(last_name: 'Johansson').find_or_create_by(first_name: 'Scarlett')
-
# # => #<User id: 2, first_name: "Scarlett", last_name: "Johansson">
-
#
-
# This method accepts a block, which is passed down to +create+. The last example
-
# above can be alternatively written this way:
-
#
-
# # Find the first user named "Scarlett" or create a new one with a
-
# # different last name.
-
# User.find_or_create_by(first_name: 'Scarlett') do |user|
-
# user.last_name = 'Johansson'
-
# end
-
# # => #<User id: 2, first_name: "Scarlett", last_name: "Johansson">
-
#
-
# This method always returns a record, but if creation was attempted and
-
# failed due to validation errors it won't be persisted, you get what
-
# +create+ returns in such situation.
-
#
-
# Please note *this method is not atomic*, it runs first a SELECT, and if
-
# there are no results an INSERT is attempted. If there are other threads
-
# or processes there is a race condition between both calls and it could
-
# be the case that you end up with two similar records.
-
#
-
# Whether that is a problem or not depends on the logic of the
-
# application, but in the particular case in which rows have a UNIQUE
-
# constraint an exception may be raised, just retry:
-
#
-
# begin
-
# CreditAccount.find_or_create_by(user_id: user.id)
-
# rescue ActiveRecord::RecordNotUnique
-
# retry
-
# end
-
#
-
1
def find_or_create_by(attributes, &block)
-
find_by(attributes) || create(attributes, &block)
-
end
-
-
# Like <tt>find_or_create_by</tt>, but calls <tt>create!</tt> so an exception
-
# is raised if the created record is invalid.
-
1
def find_or_create_by!(attributes, &block)
-
find_by(attributes) || create!(attributes, &block)
-
end
-
-
# Like <tt>find_or_create_by</tt>, but calls <tt>new</tt> instead of <tt>create</tt>.
-
1
def find_or_initialize_by(attributes, &block)
-
find_by(attributes) || new(attributes, &block)
-
end
-
-
# Runs EXPLAIN on the query or queries triggered by this relation and
-
# returns the result as a string. The string is formatted imitating the
-
# ones printed by the database shell.
-
#
-
# Note that this method actually runs the queries, since the results of some
-
# are needed by the next ones when eager loading is going on.
-
#
-
# Please see further details in the
-
# {Active Record Query Interface guide}[http://guides.rubyonrails.org/active_record_querying.html#running-explain].
-
1
def explain
-
#TODO: Fix for binds.
-
exec_explain(collecting_queries_for_explain { exec_queries })
-
end
-
-
# Converts relation objects to Array.
-
1
def to_a
-
26
load
-
26
@records
-
end
-
-
# Serializes the relation objects Array.
-
1
def encode_with(coder)
-
coder.represent_seq(nil, to_a)
-
end
-
-
1
def as_json(options = nil) #:nodoc:
-
to_a.as_json(options)
-
end
-
-
# Returns size of the records.
-
1
def size
-
loaded? ? @records.length : count(:all)
-
end
-
-
# Returns true if there are no records.
-
1
def empty?
-
return @records.empty? if loaded?
-
-
if limit_value == 0
-
true
-
else
-
c = count(:all)
-
c.respond_to?(:zero?) ? c.zero? : c.empty?
-
end
-
end
-
-
# Returns true if there are any records.
-
1
def any?
-
if block_given?
-
to_a.any? { |*block_args| yield(*block_args) }
-
else
-
!empty?
-
end
-
end
-
-
# Returns true if there is more than one record.
-
1
def many?
-
if block_given?
-
to_a.many? { |*block_args| yield(*block_args) }
-
else
-
limit_value ? to_a.many? : size > 1
-
end
-
end
-
-
# Scope all queries to the current scope.
-
#
-
# Comment.where(post_id: 1).scoping do
-
# Comment.first
-
# end
-
# # => SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 ORDER BY "comments"."id" ASC LIMIT 1
-
#
-
# Please check unscoped if you want to remove all previous scopes (including
-
# the default_scope) during the execution of a block.
-
1
def scoping
-
58
previous, klass.current_scope = klass.current_scope, self
-
58
yield
-
ensure
-
58
klass.current_scope = previous
-
end
-
-
# Updates all records in the current relation with details given. This method constructs a single SQL UPDATE
-
# statement and sends it straight to the database. It does not instantiate the involved models and it does not
-
# trigger Active Record callbacks or validations. Values passed to `update_all` will not go through
-
# ActiveRecord's type-casting behavior. It should receive only values that can be passed as-is to the SQL
-
# database.
-
#
-
# ==== Parameters
-
#
-
# * +updates+ - A string, array, or hash representing the SET part of an SQL statement.
-
#
-
# ==== Examples
-
#
-
# # Update all customers with the given attributes
-
# Customer.update_all wants_email: true
-
#
-
# # Update all books with 'Rails' in their title
-
# Book.where('title LIKE ?', '%Rails%').update_all(author: 'David')
-
#
-
# # Update all books that match conditions, but limit it to 5 ordered by date
-
# Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(author: 'David')
-
1
def update_all(updates)
-
raise ArgumentError, "Empty list of attributes to change" if updates.blank?
-
-
stmt = Arel::UpdateManager.new(arel.engine)
-
-
stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates))
-
stmt.table(table)
-
stmt.key = table[primary_key]
-
-
if joins_values.any?
-
@klass.connection.join_to_update(stmt, arel)
-
else
-
stmt.take(arel.limit)
-
stmt.order(*arel.orders)
-
stmt.wheres = arel.constraints
-
end
-
-
bvs = arel.bind_values + bind_values
-
@klass.connection.update stmt, 'SQL', bvs
-
end
-
-
# Updates an object (or multiple objects) and saves it to the database, if validations pass.
-
# The resulting object is returned whether the object was saved successfully to the database or not.
-
#
-
# ==== Parameters
-
#
-
# * +id+ - This should be the id or an array of ids to be updated.
-
# * +attributes+ - This should be a hash of attributes or an array of hashes.
-
#
-
# ==== Examples
-
#
-
# # Updates one record
-
# Person.update(15, user_name: 'Samuel', group: 'expert')
-
#
-
# # Updates multiple records
-
# people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
-
# Person.update(people.keys, people.values)
-
1
def update(id, attributes)
-
if id.is_a?(Array)
-
id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }
-
else
-
object = find(id)
-
object.update(attributes)
-
object
-
end
-
end
-
-
# Destroys the records matching +conditions+ by instantiating each
-
# record and calling its +destroy+ method. Each object's callbacks are
-
# executed (including <tt>:dependent</tt> association options). Returns the
-
# collection of objects that were destroyed; each will be frozen, to
-
# reflect that no changes should be made (since they can't be persisted).
-
#
-
# Note: Instantiation, callback execution, and deletion of each
-
# record can be time consuming when you're removing many records at
-
# once. It generates at least one SQL +DELETE+ query per record (or
-
# possibly more, to enforce your callbacks). If you want to delete many
-
# rows quickly, without concern for their associations or callbacks, use
-
# +delete_all+ instead.
-
#
-
# ==== Parameters
-
#
-
# * +conditions+ - A string, array, or hash that specifies which records
-
# to destroy. If omitted, all records are destroyed. See the
-
# Conditions section in the introduction to ActiveRecord::Base for
-
# more information.
-
#
-
# ==== Examples
-
#
-
# Person.destroy_all("last_login < '2004-04-04'")
-
# Person.destroy_all(status: "inactive")
-
# Person.where(age: 0..18).destroy_all
-
1
def destroy_all(conditions = nil)
-
if conditions
-
where(conditions).destroy_all
-
else
-
to_a.each {|object| object.destroy }.tap { reset }
-
end
-
end
-
-
# Destroy an object (or multiple objects) that has the given id. The object is instantiated first,
-
# therefore all callbacks and filters are fired off before the object is deleted. This method is
-
# less efficient than ActiveRecord#delete but allows cleanup methods and other actions to be run.
-
#
-
# This essentially finds the object (or multiple objects) with the given id, creates a new object
-
# from the attributes, and then calls destroy on it.
-
#
-
# ==== Parameters
-
#
-
# * +id+ - Can be either an Integer or an Array of Integers.
-
#
-
# ==== Examples
-
#
-
# # Destroy a single object
-
# Todo.destroy(1)
-
#
-
# # Destroy multiple objects
-
# todos = [1,2,3]
-
# Todo.destroy(todos)
-
1
def destroy(id)
-
if id.is_a?(Array)
-
id.map { |one_id| destroy(one_id) }
-
else
-
find(id).destroy
-
end
-
end
-
-
# Deletes the records matching +conditions+ without instantiating the records
-
# first, and hence not calling the +destroy+ method nor invoking callbacks. This
-
# is a single SQL DELETE statement that goes straight to the database, much more
-
# efficient than +destroy_all+. Be careful with relations though, in particular
-
# <tt>:dependent</tt> rules defined on associations are not honored. Returns the
-
# number of rows affected.
-
#
-
# Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')")
-
# Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else'])
-
# Post.where(person_id: 5).where(category: ['Something', 'Else']).delete_all
-
#
-
# Both calls delete the affected posts all at once with a single DELETE statement.
-
# If you need to destroy dependent associations or call your <tt>before_*</tt> or
-
# +after_destroy+ callbacks, use the +destroy_all+ method instead.
-
#
-
# If an invalid method is supplied, +delete_all+ raises an ActiveRecord error:
-
#
-
# Post.limit(100).delete_all
-
# # => ActiveRecord::ActiveRecordError: delete_all doesn't support limit
-
1
def delete_all(conditions = nil)
-
1
invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select { |method|
-
5
if MULTI_VALUE_METHODS.include?(method)
-
2
send("#{method}_values").any?
-
else
-
3
send("#{method}_value")
-
end
-
}
-
1
if invalid_methods.any?
-
raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
-
end
-
-
1
if conditions
-
where(conditions).delete_all
-
else
-
1
stmt = Arel::DeleteManager.new(arel.engine)
-
1
stmt.from(table)
-
-
1
if joins_values.any?
-
@klass.connection.join_to_delete(stmt, arel, table[primary_key])
-
else
-
1
stmt.wheres = arel.constraints
-
end
-
-
1
bvs = arel.bind_values + bind_values
-
1
affected = @klass.connection.delete(stmt, 'SQL', bvs)
-
-
1
reset
-
1
affected
-
end
-
end
-
-
# Deletes the row with a primary key matching the +id+ argument, using a
-
# SQL +DELETE+ statement, and returns the number of rows deleted. Active
-
# Record objects are not instantiated, so the object's callbacks are not
-
# executed, including any <tt>:dependent</tt> association options.
-
#
-
# You can delete multiple rows at once by passing an Array of <tt>id</tt>s.
-
#
-
# Note: Although it is often much faster than the alternative,
-
# <tt>#destroy</tt>, skipping callbacks might bypass business logic in
-
# your application that ensures referential integrity or performs other
-
# essential jobs.
-
#
-
# ==== Examples
-
#
-
# # Delete a single row
-
# Todo.delete(1)
-
#
-
# # Delete multiple rows
-
# Todo.delete([2,3,4])
-
1
def delete(id_or_array)
-
where(primary_key => id_or_array).delete_all
-
end
-
-
# Causes the records to be loaded from the database if they have not
-
# been loaded already. You can use this if for some reason you need
-
# to explicitly load some records before actually using them. The
-
# return value is the relation itself, not the records.
-
#
-
# Post.where(published: true).load # => #<ActiveRecord::Relation>
-
1
def load
-
26
exec_queries unless loaded?
-
-
26
self
-
end
-
-
# Forces reloading of relation.
-
1
def reload
-
reset
-
load
-
end
-
-
1
def reset
-
218
@last = @to_sql = @order_clause = @scope_for_create = @arel = @loaded = nil
-
218
@should_eager_load = @join_dependency = nil
-
218
@records = []
-
218
@offsets = {}
-
218
self
-
end
-
-
# Returns sql statement for the relation.
-
#
-
# User.where(name: 'Oscar').to_sql
-
# # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
-
1
def to_sql
-
@to_sql ||= begin
-
relation = self
-
connection = klass.connection
-
visitor = connection.visitor
-
-
if eager_loading?
-
find_with_associations { |rel| relation = rel }
-
end
-
-
arel = relation.arel
-
binds = (arel.bind_values + relation.bind_values).dup
-
binds.map! { |bv| connection.quote(*bv.reverse) }
-
collect = visitor.accept(arel.ast, Arel::Collectors::Bind.new)
-
collect.substitute_binds(binds).join
-
end
-
end
-
-
# Returns a hash of where conditions.
-
#
-
# User.where(name: 'Oscar').where_values_hash
-
# # => {name: "Oscar"}
-
1
def where_values_hash(relation_table_name = table_name)
-
equalities = where_values.grep(Arel::Nodes::Equality).find_all { |node|
-
node.left.relation.name == relation_table_name
-
}
-
-
binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
-
-
Hash[equalities.map { |where|
-
name = where.left.name
-
[name, binds.fetch(name.to_s) {
-
case where.right
-
when Array then where.right.map(&:val)
-
when Arel::Nodes::Casted
-
where.right.val
-
end
-
}]
-
}]
-
end
-
-
1
def scope_for_create
-
@scope_for_create ||= where_values_hash.merge(create_with_value)
-
end
-
-
# Returns true if relation needs eager loading.
-
1
def eager_loading?
-
@should_eager_load ||=
-
eager_load_values.any? ||
-
59
includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?)
-
end
-
-
# Joins that are also marked for preloading. In which case we should just eager load them.
-
# Note that this is a naive implementation because we could have strings and symbols which
-
# represent the same association, but that aren't matched by this. Also, we could have
-
# nested hashes which partially match, e.g. { a: :b } & { a: [:b, :c] }
-
1
def joined_includes_values
-
includes_values & joins_values
-
end
-
-
# +uniq+ and +uniq!+ are silently deprecated. +uniq_value+ delegates to +distinct_value+
-
# to maintain backwards compatibility. Use +distinct_value+ instead.
-
1
def uniq_value
-
distinct_value
-
end
-
-
# Compares two relations for equality.
-
1
def ==(other)
-
case other
-
when Associations::CollectionProxy, AssociationRelation
-
self == other.to_a
-
when Relation
-
other.to_sql == to_sql
-
when Array
-
to_a == other
-
end
-
end
-
-
1
def pretty_print(q)
-
q.pp(self.to_a)
-
end
-
-
# Returns true if relation is blank.
-
1
def blank?
-
to_a.blank?
-
end
-
-
1
def values
-
72
Hash[@values]
-
end
-
-
1
def inspect
-
entries = to_a.take([limit_value, 11].compact.min).map!(&:inspect)
-
entries[10] = '...' if entries.size == 11
-
-
"#<#{self.class.name} [#{entries.join(', ')}]>"
-
end
-
-
1
private
-
-
1
def exec_queries
-
26
@records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, arel.bind_values + bind_values)
-
-
26
preload = preload_values
-
26
preload += includes_values unless eager_loading?
-
26
preloader = build_preloader
-
26
preload.each do |associations|
-
preloader.preload @records, associations
-
end
-
-
26
@records.each { |record| record.readonly! } if readonly_value
-
-
26
@loaded = true
-
26
@records
-
end
-
-
1
def build_preloader
-
26
ActiveRecord::Associations::Preloader.new
-
end
-
-
1
def references_eager_loaded_tables?
-
joined_tables = arel.join_sources.map do |join|
-
if join.is_a?(Arel::Nodes::StringJoin)
-
tables_in_string(join.left)
-
else
-
[join.left.table_name, join.left.table_alias]
-
end
-
end
-
-
joined_tables += [table.name, table.table_alias]
-
-
# always convert table names to downcase as in Oracle quoted table names are in uppercase
-
joined_tables = joined_tables.flatten.compact.map { |t| t.downcase }.uniq
-
-
(references_values - joined_tables).any?
-
end
-
-
1
def tables_in_string(string)
-
return [] if string.blank?
-
# always convert table names to downcase as in Oracle quoted table names are in uppercase
-
# ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
-
string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map{ |s| s.downcase }.uniq - ['raw_sql_']
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Batches
-
# Looping through a collection of records from the database
-
# (using the +all+ method, for example) is very inefficient
-
# since it will try to instantiate all the objects at once.
-
#
-
# In that case, batch processing methods allow you to work
-
# with the records in batches, thereby greatly reducing memory consumption.
-
#
-
# The #find_each method uses #find_in_batches with a batch size of 1000 (or as
-
# specified by the +:batch_size+ option).
-
#
-
# Person.find_each do |person|
-
# person.do_awesome_stuff
-
# end
-
#
-
# Person.where("age > 21").find_each do |person|
-
# person.party_all_night!
-
# end
-
#
-
# If you do not provide a block to #find_each, it will return an Enumerator
-
# for chaining with other methods:
-
#
-
# Person.find_each.with_index do |person, index|
-
# person.award_trophy(index + 1)
-
# end
-
#
-
# ==== Options
-
# * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
-
# * <tt>:start</tt> - Specifies the starting point for the batch processing.
-
# This is especially useful if you want multiple workers dealing with
-
# the same processing queue. You can make worker 1 handle all the records
-
# between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
-
# (by setting the +:start+ option on that worker).
-
#
-
# # Let's process for a batch of 2000 records, skipping the first 2000 rows
-
# Person.find_each(start: 2000, batch_size: 2000) do |person|
-
# person.party_all_night!
-
# end
-
#
-
# NOTE: It's not possible to set the order. That is automatically set to
-
# ascending on the primary key ("id ASC") to make the batch ordering
-
# work. This also means that this method only works with integer-based
-
# primary keys.
-
#
-
# NOTE: You can't set the limit either, that's used to control
-
# the batch sizes.
-
1
def find_each(options = {})
-
if block_given?
-
find_in_batches(options) do |records|
-
records.each { |record| yield record }
-
end
-
else
-
enum_for :find_each, options do
-
options[:start] ? where(table[primary_key].gteq(options[:start])).size : size
-
end
-
end
-
end
-
-
# Yields each batch of records that was found by the find +options+ as
-
# an array.
-
#
-
# Person.where("age > 21").find_in_batches do |group|
-
# sleep(50) # Make sure it doesn't get too crowded in there!
-
# group.each { |person| person.party_all_night! }
-
# end
-
#
-
# If you do not provide a block to #find_in_batches, it will return an Enumerator
-
# for chaining with other methods:
-
#
-
# Person.find_in_batches.with_index do |group, batch|
-
# puts "Processing group ##{batch}"
-
# group.each(&:recover_from_last_night!)
-
# end
-
#
-
# To be yielded each record one by one, use #find_each instead.
-
#
-
# ==== Options
-
# * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
-
# * <tt>:start</tt> - Specifies the starting point for the batch processing.
-
# This is especially useful if you want multiple workers dealing with
-
# the same processing queue. You can make worker 1 handle all the records
-
# between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
-
# (by setting the +:start+ option on that worker).
-
#
-
# # Let's process the next 2000 records
-
# Person.find_in_batches(start: 2000, batch_size: 2000) do |group|
-
# group.each { |person| person.party_all_night! }
-
# end
-
#
-
# NOTE: It's not possible to set the order. That is automatically set to
-
# ascending on the primary key ("id ASC") to make the batch ordering
-
# work. This also means that this method only works with integer-based
-
# primary keys.
-
#
-
# NOTE: You can't set the limit either, that's used to control
-
# the batch sizes.
-
1
def find_in_batches(options = {})
-
options.assert_valid_keys(:start, :batch_size)
-
-
relation = self
-
start = options[:start]
-
batch_size = options[:batch_size] || 1000
-
-
unless block_given?
-
return to_enum(:find_in_batches, options) do
-
total = start ? where(table[primary_key].gteq(start)).size : size
-
(total - 1).div(batch_size) + 1
-
end
-
end
-
-
if logger && (arel.orders.present? || arel.taken.present?)
-
logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
-
end
-
-
relation = relation.reorder(batch_order).limit(batch_size)
-
records = start ? relation.where(table[primary_key].gteq(start)).to_a : relation.to_a
-
-
while records.any?
-
records_size = records.size
-
primary_key_offset = records.last.id
-
raise "Primary key not included in the custom select clause" unless primary_key_offset
-
-
yield records
-
-
break if records_size < batch_size
-
-
records = relation.where(table[primary_key].gt(primary_key_offset)).to_a
-
end
-
end
-
-
1
private
-
-
1
def batch_order
-
"#{quoted_table_name}.#{quoted_primary_key} ASC"
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Calculations
-
# Count the records.
-
#
-
# Person.count
-
# # => the total count of all people
-
#
-
# Person.count(:age)
-
# # => returns the total count of all people whose age is present in database
-
#
-
# Person.count(:all)
-
# # => performs a COUNT(*) (:all is an alias for '*')
-
#
-
# Person.distinct.count(:age)
-
# # => counts the number of different age values
-
#
-
# If +count+ is used with +group+, it returns a Hash whose keys represent the aggregated column,
-
# and the values are the respective amounts:
-
#
-
# Person.group(:city).count
-
# # => { 'Rome' => 5, 'Paris' => 3 }
-
#
-
# If +count+ is used with +group+ for multiple columns, it returns a Hash whose
-
# keys are an array containing the individual values of each column and the value
-
# of each key would be the +count+.
-
#
-
# Article.group(:status, :category).count
-
# # => {["draft", "business"]=>10, ["draft", "technology"]=>4,
-
# ["published", "business"]=>0, ["published", "technology"]=>2}
-
#
-
# If +count+ is used with +select+, it will count the selected columns:
-
#
-
# Person.select(:age).count
-
# # => counts the number of different age values
-
#
-
# Note: not all valid +select+ expressions are valid +count+ expressions. The specifics differ
-
# between databases. In invalid cases, an error from the database is thrown.
-
1
def count(column_name = nil, options = {})
-
# TODO: Remove options argument as soon we remove support to
-
# activerecord-deprecated_finders.
-
column_name, options = nil, column_name if column_name.is_a?(Hash)
-
calculate(:count, column_name, options)
-
end
-
-
# Calculates the average value on a given column. Returns +nil+ if there's
-
# no row. See +calculate+ for examples with options.
-
#
-
# Person.average(:age) # => 35.8
-
1
def average(column_name, options = {})
-
# TODO: Remove options argument as soon we remove support to
-
# activerecord-deprecated_finders.
-
calculate(:average, column_name, options)
-
end
-
-
# Calculates the minimum value on a given column. The value is returned
-
# with the same data type of the column, or +nil+ if there's no row. See
-
# +calculate+ for examples with options.
-
#
-
# Person.minimum(:age) # => 7
-
1
def minimum(column_name, options = {})
-
# TODO: Remove options argument as soon we remove support to
-
# activerecord-deprecated_finders.
-
calculate(:minimum, column_name, options)
-
end
-
-
# Calculates the maximum value on a given column. The value is returned
-
# with the same data type of the column, or +nil+ if there's no row. See
-
# +calculate+ for examples with options.
-
#
-
# Person.maximum(:age) # => 93
-
1
def maximum(column_name, options = {})
-
# TODO: Remove options argument as soon we remove support to
-
# activerecord-deprecated_finders.
-
calculate(:maximum, column_name, options)
-
end
-
-
# Calculates the sum of values on a given column. The value is returned
-
# with the same data type of the column, 0 if there's no row. See
-
# +calculate+ for examples with options.
-
#
-
# Person.sum(:age) # => 4562
-
1
def sum(*args)
-
calculate(:sum, *args)
-
end
-
-
# This calculates aggregate values in the given column. Methods for count, sum, average,
-
# minimum, and maximum have been added as shortcuts.
-
#
-
# There are two basic forms of output:
-
#
-
# * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float
-
# for AVG, and the given column's type for everything else.
-
#
-
# * Grouped values: This returns an ordered hash of the values and groups them. It
-
# takes either a column name, or the name of a belongs_to association.
-
#
-
# values = Person.group('last_name').maximum(:age)
-
# puts values["Drake"]
-
# # => 43
-
#
-
# drake = Family.find_by(last_name: 'Drake')
-
# values = Person.group(:family).maximum(:age) # Person belongs_to :family
-
# puts values[drake]
-
# # => 43
-
#
-
# values.each do |family, max_age|
-
# ...
-
# end
-
#
-
# Person.calculate(:count, :all) # The same as Person.count
-
# Person.average(:age) # SELECT AVG(age) FROM people...
-
#
-
# # Selects the minimum age for any family without any minors
-
# Person.group(:last_name).having("min(age) > 17").minimum(:age)
-
#
-
# Person.sum("2 * age")
-
1
def calculate(operation, column_name, options = {})
-
# TODO: Remove options argument as soon we remove support to
-
# activerecord-deprecated_finders.
-
if column_name.is_a?(Symbol) && attribute_alias?(column_name)
-
column_name = attribute_alias(column_name)
-
end
-
-
if has_include?(column_name)
-
construct_relation_for_association_calculations.calculate(operation, column_name, options)
-
else
-
perform_calculation(operation, column_name, options)
-
end
-
end
-
-
# Use <tt>pluck</tt> as a shortcut to select one or more attributes without
-
# loading a bunch of records just to grab the attributes you want.
-
#
-
# Person.pluck(:name)
-
#
-
# instead of
-
#
-
# Person.all.map(&:name)
-
#
-
# Pluck returns an <tt>Array</tt> of attribute values type-casted to match
-
# the plucked column names, if they can be deduced. Plucking an SQL fragment
-
# returns String values by default.
-
#
-
# Person.pluck(:id)
-
# # SELECT people.id FROM people
-
# # => [1, 2, 3]
-
#
-
# Person.pluck(:id, :name)
-
# # SELECT people.id, people.name FROM people
-
# # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
-
#
-
# Person.pluck('DISTINCT role')
-
# # SELECT DISTINCT role FROM people
-
# # => ['admin', 'member', 'guest']
-
#
-
# Person.where(age: 21).limit(5).pluck(:id)
-
# # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
-
# # => [2, 3]
-
#
-
# Person.pluck('DATEDIFF(updated_at, created_at)')
-
# # SELECT DATEDIFF(updated_at, created_at) FROM people
-
# # => ['0', '27761', '173']
-
#
-
1
def pluck(*column_names)
-
7
column_names.map! do |column_name|
-
11
if column_name.is_a?(Symbol) && attribute_alias?(column_name)
-
attribute_alias(column_name)
-
else
-
11
column_name.to_s
-
end
-
end
-
-
7
if has_include?(column_names.first)
-
construct_relation_for_association_calculations.pluck(*column_names)
-
else
-
7
relation = spawn
-
7
relation.select_values = column_names.map { |cn|
-
11
columns_hash.key?(cn) ? arel_table[cn] : cn
-
}
-
7
result = klass.connection.select_all(relation.arel, nil, relation.arel.bind_values + bind_values)
-
7
result.cast_values(klass.column_types)
-
end
-
end
-
-
# Pluck all the ID's for the relation using the table's primary key
-
#
-
# Person.ids # SELECT people.id FROM people
-
# Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.person_id = people.id
-
1
def ids
-
pluck primary_key
-
end
-
-
1
private
-
-
1
def has_include?(column_name)
-
7
eager_loading? || (includes_values.present? && ((column_name && column_name != :all) || references_eager_loaded_tables?))
-
end
-
-
1
def perform_calculation(operation, column_name, options = {})
-
# TODO: Remove options argument as soon we remove support to
-
# activerecord-deprecated_finders.
-
operation = operation.to_s.downcase
-
-
# If #count is used with #distinct / #uniq it is considered distinct. (eg. relation.distinct.count)
-
distinct = self.distinct_value
-
-
if operation == "count"
-
column_name ||= select_for_count
-
-
unless arel.ast.grep(Arel::Nodes::OuterJoin).empty?
-
distinct = true
-
end
-
-
column_name = primary_key if column_name == :all && distinct
-
distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i
-
end
-
-
if group_values.any?
-
execute_grouped_calculation(operation, column_name, distinct)
-
else
-
execute_simple_calculation(operation, column_name, distinct)
-
end
-
end
-
-
1
def aggregate_column(column_name)
-
if @klass.column_names.include?(column_name.to_s)
-
Arel::Attribute.new(@klass.unscoped.table, column_name)
-
else
-
Arel.sql(column_name == :all ? "*" : column_name.to_s)
-
end
-
end
-
-
1
def operation_over_aggregate_column(column, operation, distinct)
-
operation == 'count' ? column.count(distinct) : column.send(operation)
-
end
-
-
1
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
-
# Postgresql doesn't like ORDER BY when there are no GROUP BY
-
relation = unscope(:order)
-
-
column_alias = column_name
-
-
bind_values = nil
-
-
if operation == "count" && (relation.limit_value || relation.offset_value)
-
# Shortcut when limit is zero.
-
return 0 if relation.limit_value == 0
-
-
query_builder = build_count_subquery(relation, column_name, distinct)
-
bind_values = query_builder.bind_values + relation.bind_values
-
else
-
column = aggregate_column(column_name)
-
-
select_value = operation_over_aggregate_column(column, operation, distinct)
-
-
column_alias = select_value.alias
-
column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
-
relation.select_values = [select_value]
-
-
query_builder = relation.arel
-
bind_values = query_builder.bind_values + relation.bind_values
-
end
-
-
result = @klass.connection.select_all(query_builder, nil, bind_values)
-
row = result.first
-
value = row && row.values.first
-
column = result.column_types.fetch(column_alias) do
-
type_for(column_name)
-
end
-
-
type_cast_calculated_value(value, column, operation)
-
end
-
-
1
def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
-
group_attrs = group_values
-
-
if group_attrs.first.respond_to?(:to_sym)
-
association = @klass._reflect_on_association(group_attrs.first)
-
associated = group_attrs.size == 1 && association && association.belongs_to? # only count belongs_to associations
-
group_fields = Array(associated ? association.foreign_key : group_attrs)
-
else
-
group_fields = group_attrs
-
end
-
-
group_aliases = group_fields.map { |field|
-
column_alias_for(field)
-
}
-
group_columns = group_aliases.zip(group_fields).map { |aliaz,field|
-
[aliaz, field]
-
}
-
-
group = group_fields
-
-
if operation == 'count' && column_name == :all
-
aggregate_alias = 'count_all'
-
else
-
aggregate_alias = column_alias_for([operation, column_name].join(' '))
-
end
-
-
select_values = [
-
operation_over_aggregate_column(
-
aggregate_column(column_name),
-
operation,
-
distinct).as(aggregate_alias)
-
]
-
select_values += select_values unless having_values.empty?
-
-
select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
-
if field.respond_to?(:as)
-
field.as(aliaz)
-
else
-
"#{field} AS #{aliaz}"
-
end
-
}
-
-
relation = except(:group)
-
relation.group_values = group
-
relation.select_values = select_values
-
-
calculated_data = @klass.connection.select_all(relation, nil, relation.arel.bind_values + bind_values)
-
-
if association
-
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
-
key_records = association.klass.base_class.find(key_ids)
-
key_records = Hash[key_records.map { |r| [r.id, r] }]
-
end
-
-
Hash[calculated_data.map do |row|
-
key = group_columns.map { |aliaz, col_name|
-
column = calculated_data.column_types.fetch(aliaz) do
-
type_for(col_name)
-
end
-
type_cast_calculated_value(row[aliaz], column)
-
}
-
key = key.first if key.size == 1
-
key = key_records[key] if associated
-
-
column_type = calculated_data.column_types.fetch(aggregate_alias) { type_for(column_name) }
-
[key, type_cast_calculated_value(row[aggregate_alias], column_type, operation)]
-
end]
-
end
-
-
# Converts the given keys to the value that the database adapter returns as
-
# a usable column name:
-
#
-
# column_alias_for("users.id") # => "users_id"
-
# column_alias_for("sum(id)") # => "sum_id"
-
# column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
-
# column_alias_for("count(*)") # => "count_all"
-
# column_alias_for("count", "id") # => "count_id"
-
1
def column_alias_for(keys)
-
if keys.respond_to? :name
-
keys = "#{keys.relation.name}.#{keys.name}"
-
end
-
-
table_name = keys.to_s.downcase
-
table_name.gsub!(/\*/, 'all')
-
table_name.gsub!(/\W+/, ' ')
-
table_name.strip!
-
table_name.gsub!(/ +/, '_')
-
-
@klass.connection.table_alias_for(table_name)
-
end
-
-
1
def type_for(field)
-
field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split('.').last
-
@klass.type_for_attribute(field_name)
-
end
-
-
1
def type_cast_calculated_value(value, type, operation = nil)
-
case operation
-
when 'count' then value.to_i
-
when 'sum' then type.type_cast_from_database(value || 0)
-
when 'average' then value.respond_to?(:to_d) ? value.to_d : value
-
else type.type_cast_from_database(value)
-
end
-
end
-
-
# TODO: refactor to allow non-string `select_values` (eg. Arel nodes).
-
1
def select_for_count
-
if select_values.present?
-
select_values.join(", ")
-
else
-
:all
-
end
-
end
-
-
1
def build_count_subquery(relation, column_name, distinct)
-
column_alias = Arel.sql('count_column')
-
subquery_alias = Arel.sql('subquery_for_count')
-
-
aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
-
relation.select_values = [aliased_column]
-
arel = relation.arel
-
subquery = arel.as(subquery_alias)
-
-
sm = Arel::SelectManager.new relation.engine
-
sm.bind_values = arel.bind_values
-
select_value = operation_over_aggregate_column(column_alias, 'count', distinct)
-
sm.project(select_value).from(subquery)
-
end
-
end
-
end
-
1
require 'set'
-
1
require 'active_support/concern'
-
1
require 'active_support/deprecation'
-
-
1
module ActiveRecord
-
1
module Delegation # :nodoc:
-
1
module DelegateCache
-
1
def relation_delegate_class(klass) # :nodoc:
-
258
@relation_delegate_cache[klass]
-
end
-
-
1
def initialize_relation_delegate_cache # :nodoc:
-
4
@relation_delegate_cache = cache = {}
-
[
-
ActiveRecord::Relation,
-
ActiveRecord::Associations::CollectionProxy,
-
ActiveRecord::AssociationRelation
-
4
].each do |klass|
-
12
delegate = Class.new(klass) {
-
12
include ClassSpecificRelation
-
}
-
12
const_set klass.name.gsub('::', '_'), delegate
-
12
cache[klass] = delegate
-
end
-
end
-
-
1
def inherited(child_class)
-
4
child_class.initialize_relation_delegate_cache
-
4
super
-
end
-
end
-
-
1
extend ActiveSupport::Concern
-
-
# This module creates compiled delegation methods dynamically at runtime, which makes
-
# subsequent calls to that method faster by avoiding method_missing. The delegations
-
# may vary depending on the klass of a relation, so we create a subclass of Relation
-
# for each different klass, and the delegations are compiled into that subclass only.
-
-
1
BLACKLISTED_ARRAY_METHODS = [
-
:compact!, :flatten!, :reject!, :reverse!, :rotate!, :map!,
-
:shuffle!, :slice!, :sort!, :sort_by!, :delete_if,
-
:keep_if, :pop, :shift, :delete_at, :select!
-
].to_set # :nodoc:
-
-
1
delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join, to: :to_a
-
-
1
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
-
:connection, :columns_hash, :to => :klass
-
-
1
module ClassSpecificRelation # :nodoc:
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
12
@delegation_mutex = Mutex.new
-
end
-
-
1
module ClassMethods # :nodoc:
-
1
def name
-
superclass.name
-
end
-
-
1
def delegate_to_scoped_klass(method)
-
3
@delegation_mutex.synchronize do
-
3
return if method_defined?(method)
-
-
3
if method.to_s =~ /\A[a-zA-Z_]\w*[!?]?\z/
-
3
module_eval <<-RUBY, __FILE__, __LINE__ + 1
-
def #{method}(*args, &block)
-
scoping { @klass.#{method}(*args, &block) }
-
end
-
RUBY
-
else
-
define_method method do |*args, &block|
-
scoping { @klass.public_send(method, *args, &block) }
-
end
-
end
-
end
-
end
-
-
1
def delegate(method, opts = {})
-
@delegation_mutex.synchronize do
-
return if method_defined?(method)
-
super
-
end
-
end
-
end
-
-
1
protected
-
-
1
def method_missing(method, *args, &block)
-
3
if @klass.respond_to?(method)
-
3
self.class.delegate_to_scoped_klass(method)
-
6
scoping { @klass.public_send(method, *args, &block) }
-
elsif arel.respond_to?(method)
-
self.class.delegate method, :to => :arel
-
arel.public_send(method, *args, &block)
-
else
-
super
-
end
-
end
-
end
-
-
1
module ClassMethods # :nodoc:
-
1
def create(klass, *args)
-
258
relation_class_for(klass).new(klass, *args)
-
end
-
-
1
private
-
-
1
def relation_class_for(klass)
-
258
klass.relation_delegate_class(self)
-
end
-
end
-
-
1
def respond_to?(method, include_private = false)
-
super || @klass.respond_to?(method, include_private) ||
-
array_delegable?(method) ||
-
arel.respond_to?(method, include_private)
-
end
-
-
1
protected
-
-
1
def array_delegable?(method)
-
Array.method_defined?(method) && BLACKLISTED_ARRAY_METHODS.exclude?(method)
-
end
-
-
1
def method_missing(method, *args, &block)
-
if @klass.respond_to?(method)
-
scoping { @klass.public_send(method, *args, &block) }
-
elsif array_delegable?(method)
-
to_a.public_send(method, *args, &block)
-
elsif arel.respond_to?(method)
-
arel.public_send(method, *args, &block)
-
else
-
super
-
end
-
end
-
end
-
end
-
1
require 'active_support/deprecation'
-
1
require 'active_support/core_ext/string/filters'
-
-
1
module ActiveRecord
-
1
module FinderMethods
-
1
ONE_AS_ONE = '1 AS one'
-
-
# Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
-
# If no record can be found for all of the listed ids, then RecordNotFound will be raised. If the primary key
-
# is an integer, find by id coerces its arguments using +to_i+.
-
#
-
# Person.find(1) # returns the object for ID = 1
-
# Person.find("1") # returns the object for ID = 1
-
# Person.find("31-sarah") # returns the object for ID = 31
-
# Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
-
# Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
-
# Person.find([1]) # returns an array for the object with ID = 1
-
# Person.where("administrator = 1").order("created_on DESC").find(1)
-
#
-
# <tt>ActiveRecord::RecordNotFound</tt> will be raised if one or more ids are not found.
-
#
-
# NOTE: The returned records may not be in the same order as the ids you
-
# provide since database rows are unordered. You'd need to provide an explicit <tt>order</tt>
-
# option if you want the results are sorted.
-
#
-
# ==== Find with lock
-
#
-
# Example for find with a lock: Imagine two concurrent transactions:
-
# each will read <tt>person.visits == 2</tt>, add 1 to it, and save, resulting
-
# in two saves of <tt>person.visits = 3</tt>. By locking the row, the second
-
# transaction has to wait until the first is finished; we get the
-
# expected <tt>person.visits == 4</tt>.
-
#
-
# Person.transaction do
-
# person = Person.lock(true).find(1)
-
# person.visits += 1
-
# person.save!
-
# end
-
#
-
# ==== Variations of +find+
-
#
-
# Person.where(name: 'Spartacus', rating: 4)
-
# # returns a chainable list (which can be empty).
-
#
-
# Person.find_by(name: 'Spartacus', rating: 4)
-
# # returns the first item or nil.
-
#
-
# Person.where(name: 'Spartacus', rating: 4).first_or_initialize
-
# # returns the first item or returns a new instance (requires you call .save to persist against the database).
-
#
-
# Person.where(name: 'Spartacus', rating: 4).first_or_create
-
# # returns the first item or creates it and returns it, available since Rails 3.2.1.
-
#
-
# ==== Alternatives for +find+
-
#
-
# Person.where(name: 'Spartacus', rating: 4).exists?(conditions = :none)
-
# # returns a boolean indicating if any record with the given conditions exist.
-
#
-
# Person.where(name: 'Spartacus', rating: 4).select("field1, field2, field3")
-
# # returns a chainable list of instances with only the mentioned fields.
-
#
-
# Person.where(name: 'Spartacus', rating: 4).ids
-
# # returns an Array of ids, available since Rails 3.2.1.
-
#
-
# Person.where(name: 'Spartacus', rating: 4).pluck(:field1, :field2)
-
# # returns an Array of the required fields, available since Rails 3.1.
-
1
def find(*args)
-
if block_given?
-
to_a.find(*args) { |*block_args| yield(*block_args) }
-
else
-
find_with_ids(*args)
-
end
-
end
-
-
# Finds the first record matching the specified conditions. There
-
# is no implied ordering so if order matters, you should specify it
-
# yourself.
-
#
-
# If no record is found, returns <tt>nil</tt>.
-
#
-
# Post.find_by name: 'Spartacus', rating: 4
-
# Post.find_by "published_at < ?", 2.weeks.ago
-
1
def find_by(*args)
-
where(*args).take
-
rescue RangeError
-
nil
-
end
-
-
# Like <tt>find_by</tt>, except that if no record is found, raises
-
# an <tt>ActiveRecord::RecordNotFound</tt> error.
-
1
def find_by!(*args)
-
where(*args).take!
-
rescue RangeError
-
raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range value"
-
end
-
-
# Gives a record (or N records if a parameter is supplied) without any implied
-
# order. The order will depend on the database implementation.
-
# If an order is supplied it will be respected.
-
#
-
# Person.take # returns an object fetched by SELECT * FROM people LIMIT 1
-
# Person.take(5) # returns 5 objects fetched by SELECT * FROM people LIMIT 5
-
# Person.where(["name LIKE '%?'", name]).take
-
1
def take(limit = nil)
-
limit ? limit(limit).to_a : find_take
-
end
-
-
# Same as +take+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
-
# is found. Note that <tt>take!</tt> accepts no arguments.
-
1
def take!
-
take or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
-
end
-
-
# Find the first record (or first N records if a parameter is supplied).
-
# If no order is defined it will order by primary key.
-
#
-
# Person.first # returns the first object fetched by SELECT * FROM people ORDER BY people.id LIMIT 1
-
# Person.where(["user_name = ?", user_name]).first
-
# Person.where(["user_name = :u", { u: user_name }]).first
-
# Person.order("created_on DESC").offset(5).first
-
# Person.first(3) # returns the first three objects fetched by SELECT * FROM people ORDER BY people.id LIMIT 3
-
#
-
1
def first(limit = nil)
-
if limit
-
find_nth_with_limit(offset_index, limit)
-
else
-
find_nth(0, offset_index)
-
end
-
end
-
-
# Same as +first+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
-
# is found. Note that <tt>first!</tt> accepts no arguments.
-
1
def first!
-
find_nth! 0
-
end
-
-
# Find the last record (or last N records if a parameter is supplied).
-
# If no order is defined it will order by primary key.
-
#
-
# Person.last # returns the last object fetched by SELECT * FROM people
-
# Person.where(["user_name = ?", user_name]).last
-
# Person.order("created_on DESC").offset(5).last
-
# Person.last(3) # returns the last three objects fetched by SELECT * FROM people.
-
#
-
# Take note that in that last case, the results are sorted in ascending order:
-
#
-
# [#<Person id:2>, #<Person id:3>, #<Person id:4>]
-
#
-
# and not:
-
#
-
# [#<Person id:4>, #<Person id:3>, #<Person id:2>]
-
1
def last(limit = nil)
-
if limit
-
if order_values.empty? && primary_key
-
order(arel_table[primary_key].desc).limit(limit).reverse
-
else
-
to_a.last(limit)
-
end
-
else
-
find_last
-
end
-
end
-
-
# Same as +last+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
-
# is found. Note that <tt>last!</tt> accepts no arguments.
-
1
def last!
-
last or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
-
end
-
-
# Find the second record.
-
# If no order is defined it will order by primary key.
-
#
-
# Person.second # returns the second object fetched by SELECT * FROM people
-
# Person.offset(3).second # returns the second object from OFFSET 3 (which is OFFSET 4)
-
# Person.where(["user_name = :u", { u: user_name }]).second
-
1
def second
-
find_nth(1, offset_index)
-
end
-
-
# Same as +second+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
-
# is found.
-
1
def second!
-
find_nth! 1
-
end
-
-
# Find the third record.
-
# If no order is defined it will order by primary key.
-
#
-
# Person.third # returns the third object fetched by SELECT * FROM people
-
# Person.offset(3).third # returns the third object from OFFSET 3 (which is OFFSET 5)
-
# Person.where(["user_name = :u", { u: user_name }]).third
-
1
def third
-
find_nth(2, offset_index)
-
end
-
-
# Same as +third+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
-
# is found.
-
1
def third!
-
find_nth! 2
-
end
-
-
# Find the fourth record.
-
# If no order is defined it will order by primary key.
-
#
-
# Person.fourth # returns the fourth object fetched by SELECT * FROM people
-
# Person.offset(3).fourth # returns the fourth object from OFFSET 3 (which is OFFSET 6)
-
# Person.where(["user_name = :u", { u: user_name }]).fourth
-
1
def fourth
-
find_nth(3, offset_index)
-
end
-
-
# Same as +fourth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
-
# is found.
-
1
def fourth!
-
find_nth! 3
-
end
-
-
# Find the fifth record.
-
# If no order is defined it will order by primary key.
-
#
-
# Person.fifth # returns the fifth object fetched by SELECT * FROM people
-
# Person.offset(3).fifth # returns the fifth object from OFFSET 3 (which is OFFSET 7)
-
# Person.where(["user_name = :u", { u: user_name }]).fifth
-
1
def fifth
-
find_nth(4, offset_index)
-
end
-
-
# Same as +fifth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
-
# is found.
-
1
def fifth!
-
find_nth! 4
-
end
-
-
# Find the forty-second record. Also known as accessing "the reddit".
-
# If no order is defined it will order by primary key.
-
#
-
# Person.forty_two # returns the forty-second object fetched by SELECT * FROM people
-
# Person.offset(3).forty_two # returns the forty-second object from OFFSET 3 (which is OFFSET 44)
-
# Person.where(["user_name = :u", { u: user_name }]).forty_two
-
1
def forty_two
-
find_nth(41, offset_index)
-
end
-
-
# Same as +forty_two+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
-
# is found.
-
1
def forty_two!
-
find_nth! 41
-
end
-
-
# Returns +true+ if a record exists in the table that matches the +id+ or
-
# conditions given, or +false+ otherwise. The argument can take six forms:
-
#
-
# * Integer - Finds the record with this primary key.
-
# * String - Finds the record with a primary key corresponding to this
-
# string (such as <tt>'5'</tt>).
-
# * Array - Finds the record that matches these +find+-style conditions
-
# (such as <tt>['name LIKE ?', "%#{query}%"]</tt>).
-
# * Hash - Finds the record that matches these +find+-style conditions
-
# (such as <tt>{name: 'David'}</tt>).
-
# * +false+ - Returns always +false+.
-
# * No args - Returns +false+ if the table is empty, +true+ otherwise.
-
#
-
# For more information about specifying conditions as a hash or array,
-
# see the Conditions section in the introduction to <tt>ActiveRecord::Base</tt>.
-
#
-
# Note: You can't pass in a condition as a string (like <tt>name =
-
# 'Jamie'</tt>), since it would be sanitized and then queried against
-
# the primary key column, like <tt>id = 'name = \'Jamie\''</tt>.
-
#
-
# Person.exists?(5)
-
# Person.exists?('5')
-
# Person.exists?(['name LIKE ?', "%#{query}%"])
-
# Person.exists?(id: [1, 4, 8])
-
# Person.exists?(name: 'David')
-
# Person.exists?(false)
-
# Person.exists?
-
1
def exists?(conditions = :none)
-
36
if Base === conditions
-
conditions = conditions.id
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
You are passing an instance of ActiveRecord::Base to `exists?`.
-
Please pass the id of the object by calling `.id`
-
MSG
-
end
-
-
36
return false if !conditions
-
-
36
relation = apply_join_dependency(self, construct_join_dependency)
-
36
return false if ActiveRecord::NullRelation === relation
-
-
36
relation = relation.except(:select, :order).select(ONE_AS_ONE).limit(1)
-
-
36
case conditions
-
when Array, Hash
-
relation = relation.where(conditions)
-
else
-
36
unless conditions == :none
-
relation = relation.where(primary_key => conditions)
-
end
-
end
-
-
36
connection.select_value(relation, "#{name} Exists", relation.arel.bind_values + relation.bind_values) ? true : false
-
end
-
-
# This method is called whenever no records are found with either a single
-
# id or multiple ids and raises a +ActiveRecord::RecordNotFound+ exception.
-
#
-
# The error message is different depending on whether a single id or
-
# multiple ids are provided. If multiple ids are provided, then the number
-
# of results obtained should be provided in the +result_size+ argument and
-
# the expected number of results should be provided in the +expected_size+
-
# argument.
-
1
def raise_record_not_found_exception!(ids, result_size, expected_size) #:nodoc:
-
conditions = arel.where_sql
-
conditions = " [#{conditions}]" if conditions
-
-
if Array(ids).size == 1
-
error = "Couldn't find #{@klass.name} with '#{primary_key}'=#{ids}#{conditions}"
-
else
-
error = "Couldn't find all #{@klass.name.pluralize} with '#{primary_key}': "
-
error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})"
-
end
-
-
raise RecordNotFound, error
-
end
-
-
1
private
-
-
1
def offset_index
-
offset_value || 0
-
end
-
-
1
def find_with_associations
-
# NOTE: the JoinDependency constructed here needs to know about
-
# any joins already present in `self`, so pass them in
-
#
-
# failing to do so means that in cases like activerecord/test/cases/associations/inner_join_association_test.rb:136
-
# incorrect SQL is generated. In that case, the join dependency for
-
# SpecialCategorizations is constructed without knowledge of the
-
# preexisting join in joins_values to categorizations (by way of
-
# the `has_many :through` for categories).
-
#
-
join_dependency = construct_join_dependency(joins_values)
-
-
aliases = join_dependency.aliases
-
relation = select aliases.columns
-
relation = apply_join_dependency(relation, join_dependency)
-
-
if block_given?
-
yield relation
-
else
-
if ActiveRecord::NullRelation === relation
-
[]
-
else
-
arel = relation.arel
-
rows = connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values)
-
join_dependency.instantiate(rows, aliases)
-
end
-
end
-
end
-
-
1
def construct_join_dependency(joins = [])
-
36
including = eager_load_values + includes_values
-
36
ActiveRecord::Associations::JoinDependency.new(@klass, including, joins)
-
end
-
-
1
def construct_relation_for_association_calculations
-
from = arel.froms.first
-
if Arel::Table === from
-
apply_join_dependency(self, construct_join_dependency(joins_values))
-
else
-
# FIXME: as far as I can tell, `from` will always be an Arel::Table.
-
# There are no tests that test this branch, but presumably it's
-
# possible for `from` to be a list?
-
apply_join_dependency(self, construct_join_dependency(from))
-
end
-
end
-
-
1
def apply_join_dependency(relation, join_dependency)
-
36
relation = relation.except(:includes, :eager_load, :preload)
-
36
relation = relation.joins join_dependency
-
-
36
if using_limitable_reflections?(join_dependency.reflections)
-
36
relation
-
else
-
if relation.limit_value
-
limited_ids = limited_ids_for(relation)
-
limited_ids.empty? ? relation.none! : relation.where!(table[primary_key].in(limited_ids))
-
end
-
relation.except(:limit, :offset)
-
end
-
end
-
-
1
def limited_ids_for(relation)
-
values = @klass.connection.columns_for_distinct(
-
"#{quoted_table_name}.#{quoted_primary_key}", relation.order_values)
-
-
relation = relation.except(:select).select(values).distinct!
-
arel = relation.arel
-
-
id_rows = @klass.connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values)
-
id_rows.map {|row| row[primary_key]}
-
end
-
-
1
def using_limitable_reflections?(reflections)
-
36
reflections.none? { |r| r.collection? }
-
end
-
-
1
protected
-
-
1
def find_with_ids(*ids)
-
raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
-
-
expects_array = ids.first.kind_of?(Array)
-
return ids.first if expects_array && ids.first.empty?
-
-
ids = ids.flatten.compact.uniq
-
-
case ids.size
-
when 0
-
raise RecordNotFound, "Couldn't find #{@klass.name} without an ID"
-
when 1
-
result = find_one(ids.first)
-
expects_array ? [ result ] : result
-
else
-
find_some(ids)
-
end
-
rescue RangeError
-
raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID"
-
end
-
-
1
def find_one(id)
-
if ActiveRecord::Base === id
-
id = id.id
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
You are passing an instance of ActiveRecord::Base to `find`.
-
Please pass the id of the object by calling `.id`
-
MSG
-
end
-
-
relation = where(primary_key => id)
-
record = relation.take
-
-
raise_record_not_found_exception!(id, 0, 1) unless record
-
-
record
-
end
-
-
1
def find_some(ids)
-
result = where(primary_key => ids).to_a
-
-
expected_size =
-
if limit_value && ids.size > limit_value
-
limit_value
-
else
-
ids.size
-
end
-
-
# 11 ids with limit 3, offset 9 should give 2 results.
-
if offset_value && (ids.size - offset_value < expected_size)
-
expected_size = ids.size - offset_value
-
end
-
-
if result.size == expected_size
-
result
-
else
-
raise_record_not_found_exception!(ids, result.size, expected_size)
-
end
-
end
-
-
1
def find_take
-
if loaded?
-
@records.first
-
else
-
@take ||= limit(1).to_a.first
-
end
-
end
-
-
1
def find_nth(index, offset)
-
if loaded?
-
@records[index]
-
else
-
offset += index
-
@offsets[offset] ||= find_nth_with_limit(offset, 1).first
-
end
-
end
-
-
1
def find_nth!(index)
-
find_nth(index, offset_index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
-
end
-
-
1
def find_nth_with_limit(offset, limit)
-
relation = if order_values.empty? && primary_key
-
order(arel_table[primary_key].asc)
-
else
-
self
-
end
-
-
relation = relation.offset(offset) unless offset.zero?
-
relation.limit(limit).to_a
-
end
-
-
1
def find_last
-
if loaded?
-
@records.last
-
else
-
@last ||=
-
if limit_value
-
to_a.last
-
else
-
reverse_order.limit(1).to_a.first
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/hash/keys'
-
1
require "set"
-
-
1
module ActiveRecord
-
1
class Relation
-
1
class HashMerger # :nodoc:
-
1
attr_reader :relation, :hash
-
-
1
def initialize(relation, hash)
-
hash.assert_valid_keys(*Relation::VALUE_METHODS)
-
-
@relation = relation
-
@hash = hash
-
end
-
-
1
def merge #:nodoc:
-
Merger.new(relation, other).merge
-
end
-
-
# Applying values to a relation has some side effects. E.g.
-
# interpolation might take place for where values. So we should
-
# build a relation to merge in rather than directly merging
-
# the values.
-
1
def other
-
other = Relation.create(relation.klass, relation.table)
-
hash.each { |k, v|
-
if k == :joins
-
if Hash === v
-
other.joins!(v)
-
else
-
other.joins!(*v)
-
end
-
elsif k == :select
-
other._select!(v)
-
else
-
other.send("#{k}!", v)
-
end
-
}
-
other
-
end
-
end
-
-
1
class Merger # :nodoc:
-
1
attr_reader :relation, :values, :other
-
-
1
def initialize(relation, other)
-
@relation = relation
-
@values = other.values
-
@other = other
-
end
-
-
1
NORMAL_VALUES = Relation::SINGLE_VALUE_METHODS +
-
Relation::MULTI_VALUE_METHODS -
-
[:includes, :preload, :joins, :where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc:
-
-
-
1
def normal_values
-
NORMAL_VALUES
-
end
-
-
1
def merge
-
normal_values.each do |name|
-
value = values[name]
-
# The unless clause is here mostly for performance reasons (since the `send` call might be moderately
-
# expensive), most of the time the value is going to be `nil` or `.blank?`, the only catch is that
-
# `false.blank?` returns `true`, so there needs to be an extra check so that explicit `false` values
-
# don't fall through the cracks.
-
unless value.nil? || (value.blank? && false != value)
-
if name == :select
-
relation._select!(*value)
-
else
-
relation.send("#{name}!", *value)
-
end
-
end
-
end
-
-
merge_multi_values
-
merge_single_values
-
merge_preloads
-
merge_joins
-
-
relation
-
end
-
-
1
private
-
-
1
def merge_preloads
-
return if other.preload_values.empty? && other.includes_values.empty?
-
-
if other.klass == relation.klass
-
relation.preload!(*other.preload_values) unless other.preload_values.empty?
-
relation.includes!(other.includes_values) unless other.includes_values.empty?
-
else
-
reflection = relation.klass.reflect_on_all_associations.find do |r|
-
r.class_name == other.klass.name
-
end || return
-
-
unless other.preload_values.empty?
-
relation.preload! reflection.name => other.preload_values
-
end
-
-
unless other.includes_values.empty?
-
relation.includes! reflection.name => other.includes_values
-
end
-
end
-
end
-
-
1
def merge_joins
-
return if other.joins_values.blank?
-
-
if other.klass == relation.klass
-
relation.joins!(*other.joins_values)
-
else
-
joins_dependency, rest = other.joins_values.partition do |join|
-
case join
-
when Hash, Symbol, Array
-
true
-
else
-
false
-
end
-
end
-
-
join_dependency = ActiveRecord::Associations::JoinDependency.new(other.klass,
-
joins_dependency,
-
[])
-
relation.joins! rest
-
-
@relation = relation.joins join_dependency
-
end
-
end
-
-
1
def merge_multi_values
-
lhs_wheres = relation.where_values
-
rhs_wheres = other.where_values
-
-
lhs_binds = relation.bind_values
-
rhs_binds = other.bind_values
-
-
removed, kept = partition_overwrites(lhs_wheres, rhs_wheres)
-
-
where_values = kept + rhs_wheres
-
bind_values = filter_binds(lhs_binds, removed) + rhs_binds
-
-
relation.where_values = where_values
-
relation.bind_values = bind_values
-
-
if other.reordering_value
-
# override any order specified in the original relation
-
relation.reorder! other.order_values
-
elsif other.order_values
-
# merge in order_values from relation
-
relation.order! other.order_values
-
end
-
-
relation.extend(*other.extending_values) unless other.extending_values.blank?
-
end
-
-
1
def merge_single_values
-
relation.from_value = other.from_value unless relation.from_value
-
relation.lock_value = other.lock_value unless relation.lock_value
-
-
unless other.create_with_value.blank?
-
relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value)
-
end
-
end
-
-
1
def filter_binds(lhs_binds, removed_wheres)
-
return lhs_binds if removed_wheres.empty?
-
-
set = Set.new removed_wheres.map { |x| x.left.name.to_s }
-
lhs_binds.dup.delete_if { |col,_| set.include? col.name }
-
end
-
-
# Remove equalities from the existing relation with a LHS which is
-
# present in the relation being merged in.
-
# returns [things_to_remove, things_to_keep]
-
1
def partition_overwrites(lhs_wheres, rhs_wheres)
-
if lhs_wheres.empty? || rhs_wheres.empty?
-
return [[], lhs_wheres]
-
end
-
-
nodes = rhs_wheres.find_all do |w|
-
w.respond_to?(:operator) && w.operator == :==
-
end
-
seen = Set.new(nodes) { |node| node.left }
-
-
lhs_wheres.partition do |w|
-
w.respond_to?(:operator) && w.operator == :== && seen.include?(w.left)
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
class PredicateBuilder # :nodoc:
-
1
@handlers = []
-
-
1
autoload :RelationHandler, 'active_record/relation/predicate_builder/relation_handler'
-
1
autoload :ArrayHandler, 'active_record/relation/predicate_builder/array_handler'
-
-
1
def self.resolve_column_aliases(klass, hash)
-
# This method is a hot spot, so for now, use Hash[] to dup the hash.
-
# https://bugs.ruby-lang.org/issues/7166
-
61
hash = Hash[hash]
-
61
hash.keys.grep(Symbol) do |key|
-
97
if klass.attribute_alias? key
-
hash[klass.attribute_alias(key)] = hash.delete key
-
end
-
end
-
61
hash
-
end
-
-
1
def self.build_from_hash(klass, attributes, default_table)
-
61
queries = []
-
-
61
attributes.each do |column, value|
-
101
table = default_table
-
-
101
if value.is_a?(Hash)
-
if value.empty?
-
queries << '1=0'
-
else
-
table = Arel::Table.new(column, default_table.engine)
-
association = klass._reflect_on_association(column)
-
-
value.each do |k, v|
-
queries.concat expand(association && association.klass, table, k, v)
-
end
-
end
-
else
-
101
column = column.to_s
-
-
101
if column.include?('.')
-
table_name, column = column.split('.', 2)
-
table = Arel::Table.new(table_name, default_table.engine)
-
end
-
-
101
queries.concat expand(klass, table, column, value)
-
end
-
end
-
-
61
queries
-
end
-
-
1
def self.expand(klass, table, column, value)
-
101
queries = []
-
-
# Find the foreign key when using queries such as:
-
# Post.where(author: author)
-
#
-
# For polymorphic relationships, find the foreign key and type:
-
# PriceEstimate.where(estimate_of: treasure)
-
101
if klass && reflection = klass._reflect_on_association(column)
-
base_class = polymorphic_base_class_from_value(value)
-
-
if reflection.polymorphic? && base_class
-
queries << build(table[reflection.foreign_type], base_class)
-
end
-
-
column = reflection.foreign_key
-
-
if base_class
-
primary_key = reflection.association_primary_key(base_class)
-
value = convert_value_to_association_ids(value, primary_key)
-
end
-
end
-
-
101
queries << build(table[column], value)
-
101
queries
-
end
-
-
1
def self.polymorphic_base_class_from_value(value)
-
case value
-
when Relation
-
value.klass.base_class
-
when Array
-
val = value.compact.first
-
val.class.base_class if val.is_a?(Base)
-
when Base
-
value.class.base_class
-
end
-
end
-
-
1
def self.references(attributes)
-
attributes.map do |key, value|
-
101
if value.is_a?(Hash)
-
key
-
else
-
101
key = key.to_s
-
101
key.split('.').first if key.include?('.')
-
end
-
61
end.compact
-
end
-
-
# Define how a class is converted to Arel nodes when passed to +where+.
-
# The handler can be any object that responds to +call+, and will be used
-
# for any value that +===+ the class given. For example:
-
#
-
# MyCustomDateRange = Struct.new(:start, :end)
-
# handler = proc do |column, range|
-
# Arel::Nodes::Between.new(column,
-
# Arel::Nodes::And.new([range.start, range.end])
-
# )
-
# end
-
# ActiveRecord::PredicateBuilder.register_handler(MyCustomDateRange, handler)
-
1
def self.register_handler(klass, handler)
-
6
@handlers.unshift([klass, handler])
-
end
-
-
102
BASIC_OBJECT_HANDLER = ->(attribute, value) { attribute.eq(value) } # :nodoc:
-
1
register_handler(BasicObject, BASIC_OBJECT_HANDLER)
-
# FIXME: I think we need to deprecate this behavior
-
1
register_handler(Class, ->(attribute, value) { attribute.eq(value.name) })
-
1
register_handler(Base, ->(attribute, value) { attribute.eq(value.id) })
-
1
register_handler(Range, ->(attribute, value) { attribute.between(value) })
-
1
register_handler(Relation, RelationHandler.new)
-
1
register_handler(Array, ArrayHandler.new)
-
-
1
def self.build(attribute, value)
-
101
handler_for(value).call(attribute, value)
-
end
-
1
private_class_method :build
-
-
1
def self.handler_for(object)
-
1344
@handlers.detect { |klass, _| klass === object }.last
-
end
-
1
private_class_method :handler_for
-
-
1
def self.convert_value_to_association_ids(value, primary_key)
-
case value
-
when Relation
-
value.select(primary_key)
-
when Array
-
value.map { |v| convert_value_to_association_ids(v, primary_key) }
-
when Base
-
value._read_attribute(primary_key)
-
else
-
value
-
end
-
end
-
-
1
def self.can_be_bound?(value) # :nodoc:
-
!value.nil? &&
-
101
!value.is_a?(Hash) &&
-
handler_for(value) == BASIC_OBJECT_HANDLER
-
end
-
end
-
end
-
1
require 'active_support/core_ext/string/filters'
-
-
1
module ActiveRecord
-
1
class PredicateBuilder
-
1
class ArrayHandler # :nodoc:
-
1
def call(attribute, value)
-
values = value.map { |x| x.is_a?(Base) ? x.id : x }
-
nils, values = values.partition(&:nil?)
-
-
if values.any? { |val| val.is_a?(Array) }
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
Passing a nested array to Active Record finder methods is
-
deprecated and will be removed. Flatten your array before using
-
it for 'IN' conditions.
-
MSG
-
-
values = values.flatten
-
end
-
-
return attribute.in([]) if values.empty? && nils.empty?
-
-
ranges, values = values.partition { |v| v.is_a?(Range) }
-
-
values_predicate =
-
case values.length
-
when 0 then NullPredicate
-
when 1 then attribute.eq(values.first)
-
else attribute.in(values)
-
end
-
-
unless nils.empty?
-
values_predicate = values_predicate.or(attribute.eq(nil))
-
end
-
-
array_predicates = ranges.map { |range| attribute.between(range) }
-
array_predicates.unshift(values_predicate)
-
array_predicates.inject { |composite, predicate| composite.or(predicate) }
-
end
-
-
1
module NullPredicate # :nodoc:
-
1
def self.or(other)
-
other
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
class PredicateBuilder
-
1
class RelationHandler # :nodoc:
-
1
def call(attribute, value)
-
if value.select_values.empty?
-
value = value.select(value.klass.arel_table[value.klass.primary_key])
-
end
-
-
attribute.in(value.arel)
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/array/wrap'
-
1
require 'active_support/core_ext/string/filters'
-
1
require 'active_model/forbidden_attributes_protection'
-
-
1
module ActiveRecord
-
1
module QueryMethods
-
1
extend ActiveSupport::Concern
-
-
1
include ActiveModel::ForbiddenAttributesProtection
-
-
# WhereChain objects act as placeholder for queries in which #where does not have any parameter.
-
# In this case, #where must be chained with #not to return a new relation.
-
1
class WhereChain
-
1
def initialize(scope)
-
4
@scope = scope
-
end
-
-
# Returns a new relation expressing WHERE + NOT condition according to
-
# the conditions in the arguments.
-
#
-
# +not+ accepts conditions as a string, array, or hash. See #where for
-
# more details on each format.
-
#
-
# User.where.not("name = 'Jon'")
-
# # SELECT * FROM users WHERE NOT (name = 'Jon')
-
#
-
# User.where.not(["name = ?", "Jon"])
-
# # SELECT * FROM users WHERE NOT (name = 'Jon')
-
#
-
# User.where.not(name: "Jon")
-
# # SELECT * FROM users WHERE name != 'Jon'
-
#
-
# User.where.not(name: nil)
-
# # SELECT * FROM users WHERE name IS NOT NULL
-
#
-
# User.where.not(name: %w(Ko1 Nobu))
-
# # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
-
#
-
# User.where.not(name: "Jon", role: "admin")
-
# # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin'
-
1
def not(opts, *rest)
-
4
where_value = @scope.send(:build_where, opts, rest).map do |rel|
-
4
case rel
-
when NilClass
-
raise ArgumentError, 'Invalid argument for .where.not(), got nil.'
-
when Arel::Nodes::In
-
Arel::Nodes::NotIn.new(rel.left, rel.right)
-
when Arel::Nodes::Equality
-
4
Arel::Nodes::NotEqual.new(rel.left, rel.right)
-
when String
-
Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(rel))
-
else
-
Arel::Nodes::Not.new(rel)
-
end
-
end
-
-
4
@scope.references!(PredicateBuilder.references(opts)) if Hash === opts
-
4
@scope.where_values += where_value
-
4
@scope
-
end
-
end
-
-
1
Relation::MULTI_VALUE_METHODS.each do |name|
-
13
class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def #{name}_values # def select_values
-
@values[:#{name}] || [] # @values[:select] || []
-
end # end
-
#
-
def #{name}_values=(values) # def select_values=(values)
-
raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
-
check_cached_relation
-
@values[:#{name}] = values # @values[:select] = values
-
end # end
-
CODE
-
end
-
-
1
(Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |name|
-
9
class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def #{name}_value # def readonly_value
-
@values[:#{name}] # @values[:readonly]
-
end # end
-
CODE
-
end
-
-
1
Relation::SINGLE_VALUE_METHODS.each do |name|
-
10
class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def #{name}_value=(value) # def readonly_value=(value)
-
raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
-
check_cached_relation
-
@values[:#{name}] = value # @values[:readonly] = value
-
end # end
-
CODE
-
end
-
-
1
def check_cached_relation # :nodoc:
-
340
if defined?(@arel) && @arel
-
@arel = nil
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
Modifying already cached Relation. The cache will be reset. Use a
-
cloned Relation to prevent this warning.
-
MSG
-
end
-
end
-
-
1
def create_with_value # :nodoc:
-
@values[:create_with] || {}
-
end
-
-
1
alias extensions extending_values
-
-
# Specify relationships to be included in the result set. For
-
# example:
-
#
-
# users = User.includes(:address)
-
# users.each do |user|
-
# user.address.city
-
# end
-
#
-
# allows you to access the +address+ attribute of the +User+ model without
-
# firing an additional query. This will often result in a
-
# performance improvement over a simple +join+.
-
#
-
# You can also specify multiple relationships, like this:
-
#
-
# users = User.includes(:address, :friends)
-
#
-
# Loading nested relationships is possible using a Hash:
-
#
-
# users = User.includes(:address, friends: [:address, :followers])
-
#
-
# === conditions
-
#
-
# If you want to add conditions to your included models you'll have
-
# to explicitly reference them. For example:
-
#
-
# User.includes(:posts).where('posts.name = ?', 'example')
-
#
-
# Will throw an error, but this will work:
-
#
-
# User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
-
#
-
# Note that +includes+ works with association names while +references+ needs
-
# the actual table name.
-
1
def includes(*args)
-
check_if_method_has_arguments!(:includes, args)
-
spawn.includes!(*args)
-
end
-
-
1
def includes!(*args) # :nodoc:
-
args.reject!(&:blank?)
-
args.flatten!
-
-
self.includes_values |= args
-
self
-
end
-
-
# Forces eager loading by performing a LEFT OUTER JOIN on +args+:
-
#
-
# User.eager_load(:posts)
-
# => SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ...
-
# FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
-
# "users"."id"
-
1
def eager_load(*args)
-
check_if_method_has_arguments!(:eager_load, args)
-
spawn.eager_load!(*args)
-
end
-
-
1
def eager_load!(*args) # :nodoc:
-
self.eager_load_values += args
-
self
-
end
-
-
# Allows preloading of +args+, in the same way that +includes+ does:
-
#
-
# User.preload(:posts)
-
# => SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
-
1
def preload(*args)
-
check_if_method_has_arguments!(:preload, args)
-
spawn.preload!(*args)
-
end
-
-
1
def preload!(*args) # :nodoc:
-
self.preload_values += args
-
self
-
end
-
-
# Use to indicate that the given +table_names+ are referenced by an SQL string,
-
# and should therefore be JOINed in any query rather than loaded separately.
-
# This method only works in conjunction with +includes+.
-
# See #includes for more details.
-
#
-
# User.includes(:posts).where("posts.name = 'foo'")
-
# # => Doesn't JOIN the posts table, resulting in an error.
-
#
-
# User.includes(:posts).where("posts.name = 'foo'").references(:posts)
-
# # => Query now knows the string references posts, so adds a JOIN
-
1
def references(*table_names)
-
check_if_method_has_arguments!(:references, table_names)
-
spawn.references!(*table_names)
-
end
-
-
1
def references!(*table_names) # :nodoc:
-
61
table_names.flatten!
-
61
table_names.map!(&:to_s)
-
-
61
self.references_values |= table_names
-
61
self
-
end
-
-
# Works in two unique ways.
-
#
-
# First: takes a block so it can be used just like Array#select.
-
#
-
# Model.all.select { |m| m.field == value }
-
#
-
# This will build an array of objects from the database for the scope,
-
# converting them into an array and iterating through them using Array#select.
-
#
-
# Second: Modifies the SELECT statement for the query so that only certain
-
# fields are retrieved:
-
#
-
# Model.select(:field)
-
# # => [#<Model id: nil, field: "value">]
-
#
-
# Although in the above example it looks as though this method returns an
-
# array, it actually returns a relation object and can have other query
-
# methods appended to it, such as the other methods in ActiveRecord::QueryMethods.
-
#
-
# The argument to the method can also be an array of fields.
-
#
-
# Model.select(:field, :other_field, :and_one_more)
-
# # => [#<Model id: nil, field: "value", other_field: "value", and_one_more: "value">]
-
#
-
# You can also use one or more strings, which will be used unchanged as SELECT fields.
-
#
-
# Model.select('field AS field_one', 'other_field AS field_two')
-
# # => [#<Model id: nil, field: "value", other_field: "value">]
-
#
-
# If an alias was specified, it will be accessible from the resulting objects:
-
#
-
# Model.select('field AS field_one').first.field_one
-
# # => "value"
-
#
-
# Accessing attributes of an object that do not have fields retrieved by a select
-
# except +id+ will throw <tt>ActiveModel::MissingAttributeError</tt>:
-
#
-
# Model.select(:field).first.other_field
-
# # => ActiveModel::MissingAttributeError: missing attribute: other_field
-
1
def select(*fields)
-
36
if block_given?
-
to_a.select { |*block_args| yield(*block_args) }
-
else
-
36
raise ArgumentError, 'Call this with at least one field' if fields.empty?
-
36
spawn._select!(*fields)
-
end
-
end
-
-
1
def _select!(*fields) # :nodoc:
-
36
fields.flatten!
-
36
fields.map! do |field|
-
36
klass.attribute_alias?(field) ? klass.attribute_alias(field).to_sym : field
-
end
-
36
self.select_values += fields
-
36
self
-
end
-
-
# Allows to specify a group attribute:
-
#
-
# User.group(:name)
-
# => SELECT "users".* FROM "users" GROUP BY name
-
#
-
# Returns an array with distinct records based on the +group+ attribute:
-
#
-
# User.select([:id, :name])
-
# => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">
-
#
-
# User.group(:name)
-
# => [#<User id: 3, name: "Foo", ...>, #<User id: 2, name: "Oscar", ...>]
-
#
-
# User.group('name AS grouped_name, age')
-
# => [#<User id: 3, name: "Foo", age: 21, ...>, #<User id: 2, name: "Oscar", age: 21, ...>, #<User id: 5, name: "Foo", age: 23, ...>]
-
#
-
# Passing in an array of attributes to group by is also supported.
-
# User.select([:id, :first_name]).group(:id, :first_name).first(3)
-
# => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]
-
1
def group(*args)
-
check_if_method_has_arguments!(:group, args)
-
spawn.group!(*args)
-
end
-
-
1
def group!(*args) # :nodoc:
-
args.flatten!
-
-
self.group_values += args
-
self
-
end
-
-
# Allows to specify an order attribute:
-
#
-
# User.order(:name)
-
# => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
-
#
-
# User.order(email: :desc)
-
# => SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
-
#
-
# User.order(:name, email: :desc)
-
# => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
-
#
-
# User.order('name')
-
# => SELECT "users".* FROM "users" ORDER BY name
-
#
-
# User.order('name DESC')
-
# => SELECT "users".* FROM "users" ORDER BY name DESC
-
#
-
# User.order('name DESC, email')
-
# => SELECT "users".* FROM "users" ORDER BY name DESC, email
-
1
def order(*args)
-
check_if_method_has_arguments!(:order, args)
-
spawn.order!(*args)
-
end
-
-
1
def order!(*args) # :nodoc:
-
preprocess_order_args(args)
-
-
self.order_values += args
-
self
-
end
-
-
# Replaces any existing order defined on the relation with the specified order.
-
#
-
# User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC'
-
#
-
# Subsequent calls to order on the same relation will be appended. For example:
-
#
-
# User.order('email DESC').reorder('id ASC').order('name ASC')
-
#
-
# generates a query with 'ORDER BY id ASC, name ASC'.
-
1
def reorder(*args)
-
check_if_method_has_arguments!(:reorder, args)
-
spawn.reorder!(*args)
-
end
-
-
1
def reorder!(*args) # :nodoc:
-
preprocess_order_args(args)
-
-
self.reordering_value = true
-
self.order_values = args
-
self
-
end
-
-
1
VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
-
:limit, :offset, :joins, :includes, :from,
-
:readonly, :having])
-
-
# Removes an unwanted relation that is already defined on a chain of relations.
-
# This is useful when passing around chains of relations and would like to
-
# modify the relations without reconstructing the entire chain.
-
#
-
# User.order('email DESC').unscope(:order) == User.all
-
#
-
# The method arguments are symbols which correspond to the names of the methods
-
# which should be unscoped. The valid arguments are given in VALID_UNSCOPING_VALUES.
-
# The method can also be called with multiple arguments. For example:
-
#
-
# User.order('email DESC').select('id').where(name: "John")
-
# .unscope(:order, :select, :where) == User.all
-
#
-
# One can additionally pass a hash as an argument to unscope specific :where values.
-
# This is done by passing a hash with a single key-value pair. The key should be
-
# :where and the value should be the where value to unscope. For example:
-
#
-
# User.where(name: "John", active: true).unscope(where: :name)
-
# == User.where(active: true)
-
#
-
# This method is similar to <tt>except</tt>, but unlike
-
# <tt>except</tt>, it persists across merges:
-
#
-
# User.order('email').merge(User.except(:order))
-
# == User.order('email')
-
#
-
# User.order('email').merge(User.unscope(:order))
-
# == User.all
-
#
-
# This means it can be used in association definitions:
-
#
-
# has_many :comments, -> { unscope where: :trashed }
-
#
-
1
def unscope(*args)
-
check_if_method_has_arguments!(:unscope, args)
-
spawn.unscope!(*args)
-
end
-
-
1
def unscope!(*args) # :nodoc:
-
args.flatten!
-
self.unscope_values += args
-
-
args.each do |scope|
-
case scope
-
when Symbol
-
symbol_unscoping(scope)
-
when Hash
-
scope.each do |key, target_value|
-
if key != :where
-
raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key."
-
end
-
-
Array(target_value).each do |val|
-
where_unscoping(val)
-
end
-
end
-
else
-
raise ArgumentError, "Unrecognized scoping: #{args.inspect}. Use .unscope(where: :attribute_name) or .unscope(:order), for example."
-
end
-
end
-
-
self
-
end
-
-
# Performs a joins on +args+:
-
#
-
# User.joins(:posts)
-
# => SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
-
#
-
# You can use strings in order to customize your joins:
-
#
-
# User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id")
-
# => SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
-
1
def joins(*args)
-
36
check_if_method_has_arguments!(:joins, args)
-
36
spawn.joins!(*args)
-
end
-
-
1
def joins!(*args) # :nodoc:
-
36
args.compact!
-
36
args.flatten!
-
36
self.joins_values += args
-
36
self
-
end
-
-
1
def bind(value) # :nodoc:
-
spawn.bind!(value)
-
end
-
-
1
def bind!(value) # :nodoc:
-
self.bind_values += [value]
-
self
-
end
-
-
# Returns a new relation, which is the result of filtering the current relation
-
# according to the conditions in the arguments.
-
#
-
# #where accepts conditions in one of several formats. In the examples below, the resulting
-
# SQL is given as an illustration; the actual query generated may be different depending
-
# on the database adapter.
-
#
-
# === string
-
#
-
# A single string, without additional arguments, is passed to the query
-
# constructor as an SQL fragment, and used in the where clause of the query.
-
#
-
# Client.where("orders_count = '2'")
-
# # SELECT * from clients where orders_count = '2';
-
#
-
# Note that building your own string from user input may expose your application
-
# to injection attacks if not done properly. As an alternative, it is recommended
-
# to use one of the following methods.
-
#
-
# === array
-
#
-
# If an array is passed, then the first element of the array is treated as a template, and
-
# the remaining elements are inserted into the template to generate the condition.
-
# Active Record takes care of building the query to avoid injection attacks, and will
-
# convert from the ruby type to the database type where needed. Elements are inserted
-
# into the string in the order in which they appear.
-
#
-
# User.where(["name = ? and email = ?", "Joe", "joe@example.com"])
-
# # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
-
#
-
# Alternatively, you can use named placeholders in the template, and pass a hash as the
-
# second element of the array. The names in the template are replaced with the corresponding
-
# values from the hash.
-
#
-
# User.where(["name = :name and email = :email", { name: "Joe", email: "joe@example.com" }])
-
# # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
-
#
-
# This can make for more readable code in complex queries.
-
#
-
# Lastly, you can use sprintf-style % escapes in the template. This works slightly differently
-
# than the previous methods; you are responsible for ensuring that the values in the template
-
# are properly quoted. The values are passed to the connector for quoting, but the caller
-
# is responsible for ensuring they are enclosed in quotes in the resulting SQL. After quoting,
-
# the values are inserted using the same escapes as the Ruby core method <tt>Kernel::sprintf</tt>.
-
#
-
# User.where(["name = '%s' and email = '%s'", "Joe", "joe@example.com"])
-
# # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
-
#
-
# If #where is called with multiple arguments, these are treated as if they were passed as
-
# the elements of a single array.
-
#
-
# User.where("name = :name and email = :email", { name: "Joe", email: "joe@example.com" })
-
# # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
-
#
-
# When using strings to specify conditions, you can use any operator available from
-
# the database. While this provides the most flexibility, you can also unintentionally introduce
-
# dependencies on the underlying database. If your code is intended for general consumption,
-
# test with multiple database backends.
-
#
-
# === hash
-
#
-
# #where will also accept a hash condition, in which the keys are fields and the values
-
# are values to be searched for.
-
#
-
# Fields can be symbols or strings. Values can be single values, arrays, or ranges.
-
#
-
# User.where({ name: "Joe", email: "joe@example.com" })
-
# # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'
-
#
-
# User.where({ name: ["Alice", "Bob"]})
-
# # SELECT * FROM users WHERE name IN ('Alice', 'Bob')
-
#
-
# User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
-
# # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
-
#
-
# In the case of a belongs_to relationship, an association key can be used
-
# to specify the model if an ActiveRecord object is used as the value.
-
#
-
# author = Author.find(1)
-
#
-
# # The following queries will be equivalent:
-
# Post.where(author: author)
-
# Post.where(author_id: author)
-
#
-
# This also works with polymorphic belongs_to relationships:
-
#
-
# treasure = Treasure.create(name: 'gold coins')
-
# treasure.price_estimates << PriceEstimate.create(price: 125)
-
#
-
# # The following queries will be equivalent:
-
# PriceEstimate.where(estimate_of: treasure)
-
# PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
-
#
-
# === Joins
-
#
-
# If the relation is the result of a join, you may create a condition which uses any of the
-
# tables in the join. For string and array conditions, use the table name in the condition.
-
#
-
# User.joins(:posts).where("posts.created_at < ?", Time.now)
-
#
-
# For hash conditions, you can either use the table name in the key, or use a sub-hash.
-
#
-
# User.joins(:posts).where({ "posts.published" => true })
-
# User.joins(:posts).where({ posts: { published: true } })
-
#
-
# === no argument
-
#
-
# If no argument is passed, #where returns a new instance of WhereChain, that
-
# can be chained with #not to return a new relation that negates the where clause.
-
#
-
# User.where.not(name: "Jon")
-
# # SELECT * FROM users WHERE name != 'Jon'
-
#
-
# See WhereChain for more details on #not.
-
#
-
# === blank condition
-
#
-
# If the condition is any blank-ish object, then #where is a no-op and returns
-
# the current relation.
-
1
def where(opts = :chain, *rest)
-
98
if opts == :chain
-
4
WhereChain.new(spawn)
-
94
elsif opts.blank?
-
self
-
else
-
94
spawn.where!(opts, *rest)
-
end
-
end
-
-
1
def where!(opts, *rest) # :nodoc:
-
94
if Hash === opts
-
57
opts = sanitize_forbidden_attributes(opts)
-
57
references!(PredicateBuilder.references(opts))
-
end
-
-
94
self.where_values += build_where(opts, rest)
-
94
self
-
end
-
-
# Allows you to change a previously set where condition for a given attribute, instead of appending to that condition.
-
#
-
# Post.where(trashed: true).where(trashed: false) # => WHERE `trashed` = 1 AND `trashed` = 0
-
# Post.where(trashed: true).rewhere(trashed: false) # => WHERE `trashed` = 0
-
# Post.where(active: true).where(trashed: true).rewhere(trashed: false) # => WHERE `active` = 1 AND `trashed` = 0
-
#
-
# This is short-hand for unscope(where: conditions.keys).where(conditions). Note that unlike reorder, we're only unscoping
-
# the named conditions -- not the entire where statement.
-
1
def rewhere(conditions)
-
unscope(where: conditions.keys).where(conditions)
-
end
-
-
# Allows to specify a HAVING clause. Note that you can't use HAVING
-
# without also specifying a GROUP clause.
-
#
-
# Order.having('SUM(price) > 30').group('user_id')
-
1
def having(opts, *rest)
-
opts.blank? ? self : spawn.having!(opts, *rest)
-
end
-
-
1
def having!(opts, *rest) # :nodoc:
-
references!(PredicateBuilder.references(opts)) if Hash === opts
-
-
self.having_values += build_where(opts, rest)
-
self
-
end
-
-
# Specifies a limit for the number of records to retrieve.
-
#
-
# User.limit(10) # generated SQL has 'LIMIT 10'
-
#
-
# User.limit(10).limit(20) # generated SQL has 'LIMIT 20'
-
1
def limit(value)
-
40
spawn.limit!(value)
-
end
-
-
1
def limit!(value) # :nodoc:
-
40
self.limit_value = value
-
40
self
-
end
-
-
# Specifies the number of rows to skip before returning rows.
-
#
-
# User.offset(10) # generated SQL has "OFFSET 10"
-
#
-
# Should be used with order.
-
#
-
# User.offset(10).order("name ASC")
-
1
def offset(value)
-
spawn.offset!(value)
-
end
-
-
1
def offset!(value) # :nodoc:
-
self.offset_value = value
-
self
-
end
-
-
# Specifies locking settings (default to +true+). For more information
-
# on locking, please see +ActiveRecord::Locking+.
-
1
def lock(locks = true)
-
spawn.lock!(locks)
-
end
-
-
1
def lock!(locks = true) # :nodoc:
-
case locks
-
when String, TrueClass, NilClass
-
self.lock_value = locks || true
-
else
-
self.lock_value = false
-
end
-
-
self
-
end
-
-
# Returns a chainable relation with zero records.
-
#
-
# The returned relation implements the Null Object pattern. It is an
-
# object with defined null behavior and always returns an empty array of
-
# records without querying the database.
-
#
-
# Any subsequent condition chained to the returned relation will continue
-
# generating an empty relation and will not fire any query to the database.
-
#
-
# Used in cases where a method or scope could return zero records but the
-
# result needs to be chainable.
-
#
-
# For example:
-
#
-
# @posts = current_user.visible_posts.where(name: params[:name])
-
# # => the visible_posts method is expected to return a chainable Relation
-
#
-
# def visible_posts
-
# case role
-
# when 'Country Manager'
-
# Post.where(country: country)
-
# when 'Reviewer'
-
# Post.published
-
# when 'Bad User'
-
# Post.none # It can't be chained if [] is returned.
-
# end
-
# end
-
#
-
1
def none
-
where("1=0").extending!(NullRelation)
-
end
-
-
1
def none! # :nodoc:
-
where!("1=0").extending!(NullRelation)
-
end
-
-
# Sets readonly attributes for the returned relation. If value is
-
# true (default), attempting to update a record will result in an error.
-
#
-
# users = User.readonly
-
# users.first.save
-
# => ActiveRecord::ReadOnlyRecord: ActiveRecord::ReadOnlyRecord
-
1
def readonly(value = true)
-
spawn.readonly!(value)
-
end
-
-
1
def readonly!(value = true) # :nodoc:
-
self.readonly_value = value
-
self
-
end
-
-
# Sets attributes to be used when creating new records from a
-
# relation object.
-
#
-
# users = User.where(name: 'Oscar')
-
# users.new.name # => 'Oscar'
-
#
-
# users = users.create_with(name: 'DHH')
-
# users.new.name # => 'DHH'
-
#
-
# You can pass +nil+ to +create_with+ to reset attributes:
-
#
-
# users = users.create_with(nil)
-
# users.new.name # => 'Oscar'
-
1
def create_with(value)
-
spawn.create_with!(value)
-
end
-
-
1
def create_with!(value) # :nodoc:
-
if value
-
value = sanitize_forbidden_attributes(value)
-
self.create_with_value = create_with_value.merge(value)
-
else
-
self.create_with_value = {}
-
end
-
-
self
-
end
-
-
# Specifies table from which the records will be fetched. For example:
-
#
-
# Topic.select('title').from('posts')
-
# # => SELECT title FROM posts
-
#
-
# Can accept other relation objects. For example:
-
#
-
# Topic.select('title').from(Topic.approved)
-
# # => SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
-
#
-
# Topic.select('a.title').from(Topic.approved, :a)
-
# # => SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
-
#
-
1
def from(value, subquery_name = nil)
-
spawn.from!(value, subquery_name)
-
end
-
-
1
def from!(value, subquery_name = nil) # :nodoc:
-
self.from_value = [value, subquery_name]
-
if value.is_a? Relation
-
self.bind_values = value.arel.bind_values + value.bind_values + bind_values
-
end
-
self
-
end
-
-
# Specifies whether the records should be unique or not. For example:
-
#
-
# User.select(:name)
-
# # => Might return two records with the same name
-
#
-
# User.select(:name).distinct
-
# # => Returns 1 record per distinct name
-
#
-
# User.select(:name).distinct.distinct(false)
-
# # => You can also remove the uniqueness
-
1
def distinct(value = true)
-
spawn.distinct!(value)
-
end
-
1
alias uniq distinct
-
-
# Like #distinct, but modifies relation in place.
-
1
def distinct!(value = true) # :nodoc:
-
self.distinct_value = value
-
self
-
end
-
1
alias uniq! distinct!
-
-
# Used to extend a scope with additional methods, either through
-
# a module or through a block provided.
-
#
-
# The object returned is a relation, which can be further extended.
-
#
-
# === Using a module
-
#
-
# module Pagination
-
# def page(number)
-
# # pagination code goes here
-
# end
-
# end
-
#
-
# scope = Model.all.extending(Pagination)
-
# scope.page(params[:page])
-
#
-
# You can also pass a list of modules:
-
#
-
# scope = Model.all.extending(Pagination, SomethingElse)
-
#
-
# === Using a block
-
#
-
# scope = Model.all.extending do
-
# def page(number)
-
# # pagination code goes here
-
# end
-
# end
-
# scope.page(params[:page])
-
#
-
# You can also use a block and a module list:
-
#
-
# scope = Model.all.extending(Pagination) do
-
# def per_page(number)
-
# # pagination code goes here
-
# end
-
# end
-
1
def extending(*modules, &block)
-
if modules.any? || block
-
spawn.extending!(*modules, &block)
-
else
-
self
-
end
-
end
-
-
1
def extending!(*modules, &block) # :nodoc:
-
modules << Module.new(&block) if block
-
modules.flatten!
-
-
self.extending_values += modules
-
extend(*extending_values) if extending_values.any?
-
-
self
-
end
-
-
# Reverse the existing order clause on the relation.
-
#
-
# User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
-
1
def reverse_order
-
spawn.reverse_order!
-
end
-
-
1
def reverse_order! # :nodoc:
-
orders = order_values.uniq
-
orders.reject!(&:blank?)
-
self.order_values = reverse_sql_order(orders)
-
self
-
end
-
-
# Returns the Arel object associated with the relation.
-
1
def arel # :nodoc:
-
175
@arel ||= build_arel
-
end
-
-
1
private
-
-
1
def build_arel
-
104
arel = Arel::SelectManager.new(table.engine, table)
-
-
104
build_joins(arel, joins_values.flatten) unless joins_values.empty?
-
-
104
collapse_wheres(arel, (where_values - [''])) #TODO: Add uniq with real value comparison / ignore uniqs that have binds
-
-
104
arel.having(*having_values.uniq.reject(&:blank?)) unless having_values.empty?
-
-
104
arel.take(connection.sanitize_limit(limit_value)) if limit_value
-
104
arel.skip(offset_value.to_i) if offset_value
-
104
arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
-
-
104
build_order(arel)
-
-
104
build_select(arel)
-
-
104
arel.distinct(distinct_value)
-
104
arel.from(build_from) if from_value
-
104
arel.lock(lock_value) if lock_value
-
-
104
arel
-
end
-
-
1
def symbol_unscoping(scope)
-
if !VALID_UNSCOPING_VALUES.include?(scope)
-
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
-
end
-
-
single_val_method = Relation::SINGLE_VALUE_METHODS.include?(scope)
-
unscope_code = "#{scope}_value#{'s' unless single_val_method}="
-
-
case scope
-
when :order
-
result = []
-
when :where
-
self.bind_values = []
-
else
-
result = [] unless single_val_method
-
end
-
-
self.send(unscope_code, result)
-
end
-
-
1
def where_unscoping(target_value)
-
target_value = target_value.to_s
-
-
self.where_values = where_values.reject do |rel|
-
case rel
-
when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual
-
subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right)
-
subrelation.name == target_value
-
end
-
end
-
-
bind_values.reject! { |col,_| col.name == target_value }
-
end
-
-
1
def custom_join_ast(table, joins)
-
36
joins = joins.reject(&:blank?)
-
-
36
return [] if joins.empty?
-
-
joins.map! do |join|
-
case join
-
when Array
-
join = Arel.sql(join.join(' ')) if array_of_strings?(join)
-
when String
-
join = Arel.sql(join)
-
end
-
table.create_string_join(join)
-
end
-
end
-
-
1
def collapse_wheres(arel, wheres)
-
104
predicates = wheres.map do |where|
-
106
next where if ::Arel::Nodes::Equality === where
-
5
where = Arel.sql(where) if String === where
-
5
Arel::Nodes::Grouping.new(where)
-
end
-
-
104
arel.where(Arel::Nodes::And.new(predicates)) if predicates.present?
-
end
-
-
1
def build_where(opts, other = [])
-
98
case opts
-
when String, Array
-
[@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
-
when Hash
-
61
opts = PredicateBuilder.resolve_column_aliases(klass, opts)
-
-
61
tmp_opts, bind_values = create_binds(opts)
-
61
self.bind_values += bind_values
-
-
61
attributes = @klass.send(:expand_hash_conditions_for_aggregates, tmp_opts)
-
61
add_relations_to_bind_values(attributes)
-
-
61
PredicateBuilder.build_from_hash(klass, attributes, table)
-
else
-
37
[opts]
-
end
-
end
-
-
1
def create_binds(opts)
-
61
bindable, non_binds = opts.partition do |column, value|
-
PredicateBuilder.can_be_bound?(value) &&
-
101
@klass.columns_hash.include?(column.to_s) &&
-
!@klass.reflect_on_aggregation(column)
-
end
-
-
61
association_binds, non_binds = non_binds.partition do |column, value|
-
10
value.is_a?(Hash) && association_for_table(column)
-
end
-
-
61
new_opts = {}
-
61
binds = []
-
-
61
connection = self.connection
-
-
61
bindable.each do |(column,value)|
-
91
binds.push [@klass.columns_hash[column.to_s], value]
-
91
new_opts[column] = connection.substitute_at(column)
-
end
-
-
61
association_binds.each do |(column, value)|
-
association_relation = association_for_table(column).klass.send(:relation)
-
association_new_opts, association_bind = association_relation.send(:create_binds, value)
-
new_opts[column] = association_new_opts
-
binds += association_bind
-
end
-
-
71
non_binds.each { |column,value| new_opts[column] = value }
-
-
61
[new_opts, binds]
-
end
-
-
1
def association_for_table(table_name)
-
table_name = table_name.to_s
-
@klass._reflect_on_association(table_name) ||
-
@klass._reflect_on_association(table_name.singularize)
-
end
-
-
1
def build_from
-
opts, name = from_value
-
case opts
-
when Relation
-
name ||= 'subquery'
-
opts.arel.as(name.to_s)
-
else
-
opts
-
end
-
end
-
-
1
def build_joins(manager, joins)
-
36
buckets = joins.group_by do |join|
-
36
case join
-
when String
-
:string_join
-
when Hash, Symbol, Array
-
:association_join
-
when ActiveRecord::Associations::JoinDependency
-
36
:stashed_join
-
when Arel::Nodes::Join
-
:join_node
-
else
-
raise 'unknown class: %s' % join.class.name
-
end
-
end
-
-
36
association_joins = buckets[:association_join] || []
-
36
stashed_association_joins = buckets[:stashed_join] || []
-
36
join_nodes = (buckets[:join_node] || []).uniq
-
36
string_joins = (buckets[:string_join] || []).map(&:strip).uniq
-
-
36
join_list = join_nodes + custom_join_ast(manager, string_joins)
-
-
36
join_dependency = ActiveRecord::Associations::JoinDependency.new(
-
@klass,
-
association_joins,
-
join_list
-
)
-
-
36
join_infos = join_dependency.join_constraints stashed_association_joins
-
-
36
join_infos.each do |info|
-
info.joins.each { |join| manager.from(join) }
-
manager.bind_values.concat info.binds
-
end
-
-
36
manager.join_sources.concat(join_list)
-
-
36
manager
-
end
-
-
1
def build_select(arel)
-
104
if select_values.any?
-
43
arel.project(*arel_columns(select_values.uniq))
-
else
-
61
arel.project(@klass.arel_table[Arel.star])
-
end
-
end
-
-
1
def arel_columns(columns)
-
43
columns.map do |field|
-
47
if (Symbol === field || String === field) && columns_hash.key?(field.to_s) && !from_value
-
arel_table[field]
-
elsif Symbol === field
-
connection.quote_table_name(field.to_s)
-
else
-
47
field
-
end
-
end
-
end
-
-
1
def reverse_sql_order(order_query)
-
order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
-
-
order_query.flat_map do |o|
-
case o
-
when Arel::Nodes::Ordering
-
o.reverse
-
when String
-
o.to_s.split(',').map! do |s|
-
s.strip!
-
s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
-
end
-
else
-
o
-
end
-
end
-
end
-
-
1
def array_of_strings?(o)
-
o.is_a?(Array) && o.all? { |obj| obj.is_a?(String) }
-
end
-
-
1
def build_order(arel)
-
104
orders = order_values.uniq
-
104
orders.reject!(&:blank?)
-
-
104
arel.order(*orders) unless orders.empty?
-
end
-
-
1
VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
-
'asc', 'desc', 'ASC', 'DESC'] # :nodoc:
-
-
1
def validate_order_args(args)
-
args.each do |arg|
-
next unless arg.is_a?(Hash)
-
arg.each do |_key, value|
-
raise ArgumentError, "Direction \"#{value}\" is invalid. Valid " \
-
"directions are: #{VALID_DIRECTIONS.inspect}" unless VALID_DIRECTIONS.include?(value)
-
end
-
end
-
end
-
-
1
def preprocess_order_args(order_args)
-
order_args.flatten!
-
validate_order_args(order_args)
-
-
references = order_args.grep(String)
-
references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact!
-
references!(references) if references.any?
-
-
# if a symbol is given we prepend the quoted table name
-
order_args.map! do |arg|
-
case arg
-
when Symbol
-
arg = klass.attribute_alias(arg) if klass.attribute_alias?(arg)
-
table[arg].asc
-
when Hash
-
arg.map { |field, dir|
-
field = klass.attribute_alias(field) if klass.attribute_alias?(field)
-
table[field].send(dir.downcase)
-
}
-
else
-
arg
-
end
-
end.flatten!
-
end
-
-
# Checks to make sure that the arguments are not blank. Note that if some
-
# blank-like object were initially passed into the query method, then this
-
# method will not raise an error.
-
#
-
# Example:
-
#
-
# Post.references() # => raises an error
-
# Post.references([]) # => does not raise an error
-
#
-
# This particular method should be called with a method_name and the args
-
# passed into that method as an input. For example:
-
#
-
# def references(*args)
-
# check_if_method_has_arguments!("references", args)
-
# ...
-
# end
-
1
def check_if_method_has_arguments!(method_name, args)
-
36
if args.blank?
-
raise ArgumentError, "The method .#{method_name}() must contain arguments."
-
end
-
end
-
-
1
def add_relations_to_bind_values(attributes)
-
162
if attributes.is_a?(Hash)
-
61
attributes.each_value do |value|
-
101
if value.is_a?(ActiveRecord::Relation)
-
self.bind_values += value.arel.bind_values + value.bind_values
-
else
-
101
add_relations_to_bind_values(value)
-
end
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/hash/except'
-
1
require 'active_support/core_ext/hash/slice'
-
1
require 'active_record/relation/merger'
-
-
1
module ActiveRecord
-
1
module SpawnMethods
-
-
# This is overridden by Associations::CollectionProxy
-
1
def spawn #:nodoc:
-
217
clone
-
end
-
-
# Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an <tt>ActiveRecord::Relation</tt>.
-
# Returns an array representing the intersection of the resulting records with <tt>other</tt>, if <tt>other</tt> is an array.
-
# Post.where(published: true).joins(:comments).merge( Comment.where(spam: false) )
-
# # Performs a single join query with both where conditions.
-
#
-
# recent_posts = Post.order('created_at DESC').first(5)
-
# Post.where(published: true).merge(recent_posts)
-
# # Returns the intersection of all published posts with the 5 most recently created posts.
-
# # (This is just an example. You'd probably want to do this with a single query!)
-
#
-
# Procs will be evaluated by merge:
-
#
-
# Post.where(published: true).merge(-> { joins(:comments) })
-
# # => Post.where(published: true).joins(:comments)
-
#
-
# This is mainly intended for sharing common conditions between multiple associations.
-
1
def merge(other)
-
57
if other.is_a?(Array)
-
to_a & other
-
57
elsif other
-
spawn.merge!(other)
-
else
-
57
self
-
end
-
end
-
-
1
def merge!(other) # :nodoc:
-
if !other.is_a?(Relation) && other.respond_to?(:to_proc)
-
instance_exec(&other)
-
else
-
klass = other.is_a?(Hash) ? Relation::HashMerger : Relation::Merger
-
klass.new(self, other).merge
-
end
-
end
-
-
# Removes from the query the condition(s) specified in +skips+.
-
#
-
# Post.order('id asc').except(:order) # discards the order condition
-
# Post.where('id > 10').order('id asc').except(:where) # discards the where condition but keeps the order
-
1
def except(*skips)
-
72
relation_with values.except(*skips)
-
end
-
-
# Removes any condition from the query other than the one(s) specified in +onlies+.
-
#
-
# Post.order('id asc').only(:where) # discards the order condition
-
# Post.order('id asc').only(:where, :order) # uses the specified order
-
1
def only(*onlies)
-
if onlies.any? { |o| o == :where }
-
onlies << :bind
-
end
-
relation_with values.slice(*onlies)
-
end
-
-
1
private
-
-
1
def relation_with(values) # :nodoc:
-
72
result = Relation.create(klass, table, values)
-
72
result.extend(*extending_values) if extending_values.any?
-
72
result
-
end
-
end
-
end
-
1
module ActiveRecord
-
###
-
# This class encapsulates a Result returned from calling +exec_query+ on any
-
# database connection adapter. For example:
-
#
-
# result = ActiveRecord::Base.connection.exec_query('SELECT id, title, body FROM posts')
-
# result # => #<ActiveRecord::Result:0xdeadbeef>
-
#
-
# # Get the column names of the result:
-
# result.columns
-
# # => ["id", "title", "body"]
-
#
-
# # Get the record values of the result:
-
# result.rows
-
# # => [[1, "title_1", "body_1"],
-
# [2, "title_2", "body_2"],
-
# ...
-
# ]
-
#
-
# # Get an array of hashes representing the result (column => value):
-
# result.to_hash
-
# # => [{"id" => 1, "title" => "title_1", "body" => "body_1"},
-
# {"id" => 2, "title" => "title_2", "body" => "body_2"},
-
# ...
-
# ]
-
#
-
# # ActiveRecord::Result also includes Enumerable.
-
# result.each do |row|
-
# puts row['title'] + " " + row['body']
-
# end
-
1
class Result
-
1
include Enumerable
-
-
1
IDENTITY_TYPE = Type::Value.new # :nodoc:
-
-
1
attr_reader :columns, :rows, :column_types
-
-
1
def initialize(columns, rows, column_types = {})
-
181
@columns = columns
-
181
@rows = rows
-
181
@hash_rows = nil
-
181
@column_types = column_types
-
end
-
-
1
def length
-
50
@rows.length
-
end
-
-
1
def each
-
136
if block_given?
-
224
hash_rows.each { |row| yield row }
-
else
-
hash_rows.to_enum { @rows.size }
-
end
-
end
-
-
1
def to_hash
-
7
hash_rows
-
end
-
-
1
alias :map! :map
-
1
alias :collect! :map
-
-
# Returns true if there are no records.
-
1
def empty?
-
rows.empty?
-
end
-
-
1
def to_ary
-
hash_rows
-
end
-
-
1
def [](idx)
-
hash_rows[idx]
-
end
-
-
1
def last
-
hash_rows.last
-
end
-
-
1
def cast_values(type_overrides = {}) # :nodoc:
-
18
types = columns.map { |name| column_type(name, type_overrides) }
-
7
result = rows.map do |values|
-
2
types.zip(values).map { |type, value| type.type_cast_from_database(value) }
-
end
-
-
7
columns.one? ? result.map!(&:first) : result
-
end
-
-
1
def initialize_copy(other)
-
@columns = columns.dup
-
@rows = rows.dup
-
@column_types = column_types.dup
-
@hash_rows = nil
-
end
-
-
1
private
-
-
1
def column_type(name, type_overrides = {})
-
11
type_overrides.fetch(name) do
-
column_types.fetch(name, IDENTITY_TYPE)
-
end
-
end
-
-
1
def hash_rows
-
@hash_rows ||=
-
begin
-
# We freeze the strings to prevent them getting duped when
-
# used as keys in ActiveRecord::Base's @attributes hash
-
966
columns = @columns.map { |c| c.dup.freeze }
-
143
@rows.map { |row|
-
# In the past we used Hash[columns.zip(row)]
-
# though elegant, the verbose way is much more efficient
-
# both time and memory wise cause it avoids a big array allocation
-
# this method is called a lot and needs to be micro optimised
-
157
hash = {}
-
-
157
index = 0
-
157
length = columns.length
-
-
157
while index < length
-
848
hash[columns[index]] = row[index]
-
848
index += 1
-
end
-
-
157
hash
-
}
-
143
end
-
end
-
end
-
end
-
1
require 'active_support/per_thread_registry'
-
-
1
module ActiveRecord
-
# This is a thread locals registry for Active Record. For example:
-
#
-
# ActiveRecord::RuntimeRegistry.connection_handler
-
#
-
# returns the connection handler local to the current thread.
-
#
-
# See the documentation of <tt>ActiveSupport::PerThreadRegistry</tt>
-
# for further details.
-
1
class RuntimeRegistry # :nodoc:
-
1
extend ActiveSupport::PerThreadRegistry
-
-
1
attr_accessor :connection_handler, :sql_runtime, :connection_id
-
-
1
[:connection_handler, :sql_runtime, :connection_id].each do |val|
-
3
class_eval %{ def self.#{val}; instance.#{val}; end }, __FILE__, __LINE__
-
3
class_eval %{ def self.#{val}=(x); instance.#{val}=x; end }, __FILE__, __LINE__
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Sanitization
-
1
extend ActiveSupport::Concern
-
-
1
module ClassMethods
-
1
def quote_value(value, column) #:nodoc:
-
connection.quote(value, column)
-
end
-
-
# Used to sanitize objects before they're used in an SQL SELECT statement. Delegates to <tt>connection.quote</tt>.
-
1
def sanitize(object) #:nodoc:
-
connection.quote(object)
-
end
-
-
1
protected
-
-
# Accepts an array, hash, or string of SQL conditions and sanitizes
-
# them into a valid SQL fragment for a WHERE clause.
-
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
-
# { name: "foo'bar", group_id: 4 } returns "name='foo''bar' and group_id='4'"
-
# "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
-
1
def sanitize_sql_for_conditions(condition, table_name = self.table_name)
-
50
return nil if condition.blank?
-
-
50
case condition
-
when Array; sanitize_sql_array(condition)
-
when Hash; sanitize_sql_hash_for_conditions(condition, table_name)
-
50
else condition
-
end
-
end
-
1
alias_method :sanitize_sql, :sanitize_sql_for_conditions
-
1
alias_method :sanitize_conditions, :sanitize_sql
-
-
# Accepts an array, hash, or string of SQL conditions and sanitizes
-
# them into a valid SQL fragment for a SET clause.
-
# { name: nil, group_id: 4 } returns "name = NULL , group_id='4'"
-
1
def sanitize_sql_for_assignment(assignments, default_table_name = self.table_name)
-
case assignments
-
when Array; sanitize_sql_array(assignments)
-
when Hash; sanitize_sql_hash_for_assignment(assignments, default_table_name)
-
else assignments
-
end
-
end
-
-
# Accepts a hash of SQL conditions and replaces those attributes
-
# that correspond to a +composed_of+ relationship with their expanded
-
# aggregate attribute values.
-
# Given:
-
# class Person < ActiveRecord::Base
-
# composed_of :address, class_name: "Address",
-
# mapping: [%w(address_street street), %w(address_city city)]
-
# end
-
# Then:
-
# { address: Address.new("813 abc st.", "chicago") }
-
# # => { address_street: "813 abc st.", address_city: "chicago" }
-
1
def expand_hash_conditions_for_aggregates(attrs)
-
61
expanded_attrs = {}
-
61
attrs.each do |attr, value|
-
101
if aggregation = reflect_on_aggregation(attr.to_sym)
-
mapping = aggregation.mapping
-
mapping.each do |field_attr, aggregate_attr|
-
if mapping.size == 1 && !value.respond_to?(aggregate_attr)
-
expanded_attrs[field_attr] = value
-
else
-
expanded_attrs[field_attr] = value.send(aggregate_attr)
-
end
-
end
-
else
-
101
expanded_attrs[attr] = value
-
end
-
end
-
61
expanded_attrs
-
end
-
-
# Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause.
-
# { name: "foo'bar", group_id: 4 }
-
# # => "name='foo''bar' and group_id= 4"
-
# { status: nil, group_id: [1,2,3] }
-
# # => "status IS NULL and group_id IN (1,2,3)"
-
# { age: 13..18 }
-
# # => "age BETWEEN 13 AND 18"
-
# { 'other_records.id' => 7 }
-
# # => "`other_records`.`id` = 7"
-
# { other_records: { id: 7 } }
-
# # => "`other_records`.`id` = 7"
-
# And for value objects on a composed_of relationship:
-
# { address: Address.new("123 abc st.", "chicago") }
-
# # => "address_street='123 abc st.' and address_city='chicago'"
-
1
def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
-
ActiveSupport::Deprecation.warn(<<-EOWARN)
-
sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
-
EOWARN
-
attrs = PredicateBuilder.resolve_column_aliases self, attrs
-
attrs = expand_hash_conditions_for_aggregates(attrs)
-
-
table = Arel::Table.new(table_name, arel_engine).alias(default_table_name)
-
PredicateBuilder.build_from_hash(self, attrs, table).map { |b|
-
connection.visitor.compile b
-
}.join(' AND ')
-
end
-
1
alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
-
-
# Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
-
# { status: nil, group_id: 1 }
-
# # => "status = NULL , group_id = 1"
-
1
def sanitize_sql_hash_for_assignment(attrs, table)
-
c = connection
-
attrs.map do |attr, value|
-
"#{c.quote_table_name_for_assignment(table, attr)} = #{quote_bound_value(value, c, columns_hash[attr.to_s])}"
-
end.join(', ')
-
end
-
-
# Sanitizes a +string+ so that it is safe to use within an SQL
-
# LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%"
-
1
def sanitize_sql_like(string, escape_character = "\\")
-
pattern = Regexp.union(escape_character, "%", "_")
-
string.gsub(pattern) { |x| [escape_character, x].join }
-
end
-
-
# Accepts an array of conditions. The array has each value
-
# sanitized and interpolated into the SQL statement.
-
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
-
1
def sanitize_sql_array(ary)
-
statement, *values = ary
-
if values.first.is_a?(Hash) && statement =~ /:\w+/
-
replace_named_bind_variables(statement, values.first)
-
elsif statement.include?('?')
-
replace_bind_variables(statement, values)
-
elsif statement.blank?
-
statement
-
else
-
statement % values.collect { |value| connection.quote_string(value.to_s) }
-
end
-
end
-
-
1
def replace_bind_variables(statement, values) #:nodoc:
-
raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
-
bound = values.dup
-
c = connection
-
statement.gsub(/\?/) do
-
replace_bind_variable(bound.shift, c)
-
end
-
end
-
-
1
def replace_bind_variable(value, c = connection) #:nodoc:
-
if ActiveRecord::Relation === value
-
value.to_sql
-
else
-
quote_bound_value(value, c)
-
end
-
end
-
-
1
def replace_named_bind_variables(statement, bind_vars) #:nodoc:
-
statement.gsub(/(:?):([a-zA-Z]\w*)/) do
-
if $1 == ':' # skip postgresql casts
-
$& # return the whole match
-
elsif bind_vars.include?(match = $2.to_sym)
-
replace_bind_variable(bind_vars[match])
-
else
-
raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
-
end
-
end
-
end
-
-
1
def quote_bound_value(value, c = connection, column = nil) #:nodoc:
-
if column
-
c.quote(value, column)
-
elsif value.respond_to?(:map) && !value.acts_like?(:string)
-
if value.respond_to?(:empty?) && value.empty?
-
c.quote(nil)
-
else
-
value.map { |v| c.quote(v) }.join(',')
-
end
-
else
-
c.quote(value)
-
end
-
end
-
-
1
def raise_if_bind_arity_mismatch(statement, expected, provided) #:nodoc:
-
unless expected == provided
-
raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
-
end
-
end
-
end
-
-
# TODO: Deprecate this
-
1
def quoted_id
-
self.class.quote_value(id, column_for_attribute(self.class.primary_key))
-
end
-
end
-
end
-
1
require 'active_record/scoping/default'
-
1
require 'active_record/scoping/named'
-
1
require 'active_record/base'
-
-
1
module ActiveRecord
-
1
class SchemaMigration < ActiveRecord::Base
-
1
class << self
-
1
def primary_key
-
nil
-
end
-
-
1
def table_name
-
7
"#{table_name_prefix}#{ActiveRecord::Base.schema_migrations_table_name}#{table_name_suffix}"
-
end
-
-
1
def index_name
-
"#{table_name_prefix}unique_#{ActiveRecord::Base.schema_migrations_table_name}#{table_name_suffix}"
-
end
-
-
1
def table_exists?
-
connection.table_exists?(table_name)
-
end
-
-
1
def create_table(limit=nil)
-
unless table_exists?
-
version_options = {null: false}
-
version_options[:limit] = limit if limit
-
-
connection.create_table(table_name, id: false) do |t|
-
t.column :version, :string, version_options
-
end
-
connection.add_index table_name, :version, unique: true, name: index_name
-
end
-
end
-
-
1
def drop_table
-
if table_exists?
-
connection.remove_index table_name, name: index_name
-
connection.drop_table(table_name)
-
end
-
end
-
-
1
def normalize_migration_number(number)
-
"%.3d" % number.to_i
-
end
-
-
1
def normalized_versions
-
pluck(:version).map { |v| normalize_migration_number v }
-
end
-
end
-
-
1
def version
-
2
super.to_i
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Scoping
-
1
module Default
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
# Stores the default scope for the class.
-
1
class_attribute :default_scopes, instance_writer: false, instance_predicate: false
-
-
1
self.default_scopes = []
-
end
-
-
1
module ClassMethods
-
# Returns a scope for the model without the previously set scopes.
-
#
-
# class Post < ActiveRecord::Base
-
# def self.default_scope
-
# where published: true
-
# end
-
# end
-
#
-
# Post.all # Fires "SELECT * FROM posts WHERE published = true"
-
# Post.unscoped.all # Fires "SELECT * FROM posts"
-
# Post.where(published: false).unscoped.all # Fires "SELECT * FROM posts"
-
#
-
# This method also accepts a block. All queries inside the block will
-
# not use the previously set scopes.
-
#
-
# Post.unscoped {
-
# Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
-
# }
-
1
def unscoped
-
69
block_given? ? relation.scoping { yield } : relation
-
end
-
-
1
def before_remove_const #:nodoc:
-
self.current_scope = nil
-
end
-
-
1
protected
-
-
# Use this macro in your model to set a default scope for all operations on
-
# the model.
-
#
-
# class Article < ActiveRecord::Base
-
# default_scope { where(published: true) }
-
# end
-
#
-
# Article.all # => SELECT * FROM articles WHERE published = true
-
#
-
# The +default_scope+ is also applied while creating/building a record.
-
# It is not applied while updating a record.
-
#
-
# Article.new.published # => true
-
# Article.create.published # => true
-
#
-
# (You can also pass any object which responds to +call+ to the
-
# +default_scope+ macro, and it will be called when building the
-
# default scope.)
-
#
-
# If you use multiple +default_scope+ declarations in your model then
-
# they will be merged together:
-
#
-
# class Article < ActiveRecord::Base
-
# default_scope { where(published: true) }
-
# default_scope { where(rating: 'G') }
-
# end
-
#
-
# Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G'
-
#
-
# This is also the case with inheritance and module includes where the
-
# parent or module defines a +default_scope+ and the child or including
-
# class defines a second one.
-
#
-
# If you need to do more complex things with a default scope, you can
-
# alternatively define it as a class method:
-
#
-
# class Article < ActiveRecord::Base
-
# def self.default_scope
-
# # Should return a scope, you can call 'super' here etc.
-
# end
-
# end
-
1
def default_scope(scope = nil)
-
scope = Proc.new if block_given?
-
-
if scope.is_a?(Relation) || !scope.respond_to?(:call)
-
raise ArgumentError,
-
"Support for calling #default_scope without a block is removed. For example instead " \
-
"of `default_scope where(color: 'red')`, please use " \
-
"`default_scope { where(color: 'red') }`. (Alternatively you can just redefine " \
-
"self.default_scope.)"
-
end
-
-
self.default_scopes += [scope]
-
end
-
-
1
def build_default_scope(base_rel = relation) # :nodoc:
-
57
return if abstract_class?
-
57
if !Base.is_a?(method(:default_scope).owner)
-
# The user has defined their own default scope method, so call that
-
evaluate_default_scope { default_scope }
-
57
elsif default_scopes.any?
-
evaluate_default_scope do
-
default_scopes.inject(base_rel) do |default_scope, scope|
-
default_scope.merge(base_rel.scoping { scope.call })
-
end
-
end
-
end
-
end
-
-
1
def ignore_default_scope? # :nodoc:
-
ScopeRegistry.value_for(:ignore_default_scope, self)
-
end
-
-
1
def ignore_default_scope=(ignore) # :nodoc:
-
ScopeRegistry.set_value_for(:ignore_default_scope, self, ignore)
-
end
-
-
# The ignore_default_scope flag is used to prevent an infinite recursion
-
# situation where a default scope references a scope which has a default
-
# scope which references a scope...
-
1
def evaluate_default_scope # :nodoc:
-
return if ignore_default_scope?
-
-
begin
-
self.ignore_default_scope = true
-
yield
-
ensure
-
self.ignore_default_scope = false
-
end
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/array'
-
1
require 'active_support/core_ext/hash/except'
-
1
require 'active_support/core_ext/kernel/singleton_class'
-
-
1
module ActiveRecord
-
# = Active Record \Named \Scopes
-
1
module Scoping
-
1
module Named
-
1
extend ActiveSupport::Concern
-
-
1
module ClassMethods
-
# Returns an <tt>ActiveRecord::Relation</tt> scope object.
-
#
-
# posts = Post.all
-
# posts.size # Fires "select count(*) from posts" and returns the count
-
# posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects
-
#
-
# fruits = Fruit.all
-
# fruits = fruits.where(color: 'red') if options[:red_only]
-
# fruits = fruits.limit(10) if limited?
-
#
-
# You can define a scope that applies to all finders using
-
# <tt>ActiveRecord::Base.default_scope</tt>.
-
1
def all
-
57
if current_scope
-
current_scope.clone
-
else
-
57
default_scoped
-
end
-
end
-
-
1
def default_scoped # :nodoc:
-
57
relation.merge(build_default_scope)
-
end
-
-
# Collects attributes from scopes that should be applied when creating
-
# an AR instance for the particular class this is called on.
-
1
def scope_attributes # :nodoc:
-
all.scope_for_create
-
end
-
-
# Are there default attributes associated with this scope?
-
1
def scope_attributes? # :nodoc:
-
43
current_scope || default_scopes.any?
-
end
-
-
# Adds a class method for retrieving and querying objects. A \scope
-
# represents a narrowing of a database query, such as
-
# <tt>where(color: :red).select('shirts.*').includes(:washing_instructions)</tt>.
-
#
-
# class Shirt < ActiveRecord::Base
-
# scope :red, -> { where(color: 'red') }
-
# scope :dry_clean_only, -> { joins(:washing_instructions).where('washing_instructions.dry_clean_only = ?', true) }
-
# end
-
#
-
# The above calls to +scope+ define class methods <tt>Shirt.red</tt> and
-
# <tt>Shirt.dry_clean_only</tt>. <tt>Shirt.red</tt>, in effect,
-
# represents the query <tt>Shirt.where(color: 'red')</tt>.
-
#
-
# You should always pass a callable object to the scopes defined
-
# with +scope+. This ensures that the scope is re-evaluated each
-
# time it is called.
-
#
-
# Note that this is simply 'syntactic sugar' for defining an actual
-
# class method:
-
#
-
# class Shirt < ActiveRecord::Base
-
# def self.red
-
# where(color: 'red')
-
# end
-
# end
-
#
-
# Unlike <tt>Shirt.find(...)</tt>, however, the object returned by
-
# <tt>Shirt.red</tt> is not an Array; it resembles the association object
-
# constructed by a +has_many+ declaration. For instance, you can invoke
-
# <tt>Shirt.red.first</tt>, <tt>Shirt.red.count</tt>,
-
# <tt>Shirt.red.where(size: 'small')</tt>. Also, just as with the
-
# association objects, named \scopes act like an Array, implementing
-
# Enumerable; <tt>Shirt.red.each(&block)</tt>, <tt>Shirt.red.first</tt>,
-
# and <tt>Shirt.red.inject(memo, &block)</tt> all behave as if
-
# <tt>Shirt.red</tt> really was an Array.
-
#
-
# These named \scopes are composable. For instance,
-
# <tt>Shirt.red.dry_clean_only</tt> will produce all shirts that are
-
# both red and dry clean only. Nested finds and calculations also work
-
# with these compositions: <tt>Shirt.red.dry_clean_only.count</tt>
-
# returns the number of garments for which these criteria obtain.
-
# Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
-
#
-
# All scopes are available as class methods on the ActiveRecord::Base
-
# descendant upon which the \scopes were defined. But they are also
-
# available to +has_many+ associations. If,
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :shirts
-
# end
-
#
-
# then <tt>elton.shirts.red.dry_clean_only</tt> will return all of
-
# Elton's red, dry clean only shirts.
-
#
-
# \Named scopes can also have extensions, just as with +has_many+
-
# declarations:
-
#
-
# class Shirt < ActiveRecord::Base
-
# scope :red, -> { where(color: 'red') } do
-
# def dom_id
-
# 'red_shirts'
-
# end
-
# end
-
# end
-
#
-
# Scopes can also be used while creating/building a record.
-
#
-
# class Article < ActiveRecord::Base
-
# scope :published, -> { where(published: true) }
-
# end
-
#
-
# Article.published.new.published # => true
-
# Article.published.create.published # => true
-
#
-
# \Class methods on your model are automatically available
-
# on scopes. Assuming the following setup:
-
#
-
# class Article < ActiveRecord::Base
-
# scope :published, -> { where(published: true) }
-
# scope :featured, -> { where(featured: true) }
-
#
-
# def self.latest_article
-
# order('published_at desc').first
-
# end
-
#
-
# def self.titles
-
# pluck(:title)
-
# end
-
# end
-
#
-
# We are able to call the methods like this:
-
#
-
# Article.published.featured.latest_article
-
# Article.featured.titles
-
1
def scope(name, body, &block)
-
unless body.respond_to?(:call)
-
raise ArgumentError, 'The scope body needs to be callable.'
-
end
-
-
if dangerous_class_method?(name)
-
raise ArgumentError, "You tried to define a scope named \"#{name}\" " \
-
"on the model \"#{self.name}\", but Active Record already defined " \
-
"a class method with the same name."
-
end
-
-
extension = Module.new(&block) if block
-
-
singleton_class.send(:define_method, name) do |*args|
-
scope = all.scoping { body.call(*args) }
-
scope = scope.extending(extension) if extension
-
-
scope || all
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord #:nodoc:
-
# = Active Record Serialization
-
1
module Serialization
-
1
extend ActiveSupport::Concern
-
1
include ActiveModel::Serializers::JSON
-
-
1
included do
-
1
self.include_root_in_json = false
-
end
-
-
1
def serializable_hash(options = nil)
-
options = options.try(:clone) || {}
-
-
options[:except] = Array(options[:except]).map { |n| n.to_s }
-
options[:except] |= Array(self.class.inheritance_column)
-
-
super(options)
-
end
-
end
-
end
-
-
1
require 'active_record/serializers/xml_serializer'
-
1
require 'active_support/core_ext/hash/conversions'
-
-
1
module ActiveRecord #:nodoc:
-
1
module Serialization
-
1
include ActiveModel::Serializers::Xml
-
-
# Builds an XML document to represent the model. Some configuration is
-
# available through +options+. However more complicated cases should
-
# override ActiveRecord::Base#to_xml.
-
#
-
# By default the generated XML document will include the processing
-
# instruction and all the object's attributes. For example:
-
#
-
# <?xml version="1.0" encoding="UTF-8"?>
-
# <topic>
-
# <title>The First Topic</title>
-
# <author-name>David</author-name>
-
# <id type="integer">1</id>
-
# <approved type="boolean">false</approved>
-
# <replies-count type="integer">0</replies-count>
-
# <bonus-time type="dateTime">2000-01-01T08:28:00+12:00</bonus-time>
-
# <written-on type="dateTime">2003-07-16T09:28:00+1200</written-on>
-
# <content>Have a nice day</content>
-
# <author-email-address>david@loudthinking.com</author-email-address>
-
# <parent-id></parent-id>
-
# <last-read type="date">2004-04-15</last-read>
-
# </topic>
-
#
-
# This behavior can be controlled with <tt>:only</tt>, <tt>:except</tt>,
-
# <tt>:skip_instruct</tt>, <tt>:skip_types</tt>, <tt>:dasherize</tt> and <tt>:camelize</tt> .
-
# The <tt>:only</tt> and <tt>:except</tt> options are the same as for the
-
# +attributes+ method. The default is to dasherize all column names, but you
-
# can disable this setting <tt>:dasherize</tt> to +false+. Setting <tt>:camelize</tt>
-
# to +true+ will camelize all column names - this also overrides <tt>:dasherize</tt>.
-
# To not have the column type included in the XML output set <tt>:skip_types</tt> to +true+.
-
#
-
# For instance:
-
#
-
# topic.to_xml(skip_instruct: true, except: [ :id, :bonus_time, :written_on, :replies_count ])
-
#
-
# <topic>
-
# <title>The First Topic</title>
-
# <author-name>David</author-name>
-
# <approved type="boolean">false</approved>
-
# <content>Have a nice day</content>
-
# <author-email-address>david@loudthinking.com</author-email-address>
-
# <parent-id></parent-id>
-
# <last-read type="date">2004-04-15</last-read>
-
# </topic>
-
#
-
# To include first level associations use <tt>:include</tt>:
-
#
-
# firm.to_xml include: [ :account, :clients ]
-
#
-
# <?xml version="1.0" encoding="UTF-8"?>
-
# <firm>
-
# <id type="integer">1</id>
-
# <rating type="integer">1</rating>
-
# <name>37signals</name>
-
# <clients type="array">
-
# <client>
-
# <rating type="integer">1</rating>
-
# <name>Summit</name>
-
# </client>
-
# <client>
-
# <rating type="integer">1</rating>
-
# <name>Microsoft</name>
-
# </client>
-
# </clients>
-
# <account>
-
# <id type="integer">1</id>
-
# <credit-limit type="integer">50</credit-limit>
-
# </account>
-
# </firm>
-
#
-
# Additionally, the record being serialized will be passed to a Proc's second
-
# parameter. This allows for ad hoc additions to the resultant document that
-
# incorporate the context of the record being serialized. And by leveraging the
-
# closure created by a Proc, to_xml can be used to add elements that normally fall
-
# outside of the scope of the model -- for example, generating and appending URLs
-
# associated with models.
-
#
-
# proc = Proc.new { |options, record| options[:builder].tag!('name-reverse', record.name.reverse) }
-
# firm.to_xml procs: [ proc ]
-
#
-
# <firm>
-
# # ... normal attributes as shown above ...
-
# <name-reverse>slangis73</name-reverse>
-
# </firm>
-
#
-
# To include deeper levels of associations pass a hash like this:
-
#
-
# firm.to_xml include: {account: {}, clients: {include: :address}}
-
# <?xml version="1.0" encoding="UTF-8"?>
-
# <firm>
-
# <id type="integer">1</id>
-
# <rating type="integer">1</rating>
-
# <name>37signals</name>
-
# <clients type="array">
-
# <client>
-
# <rating type="integer">1</rating>
-
# <name>Summit</name>
-
# <address>
-
# ...
-
# </address>
-
# </client>
-
# <client>
-
# <rating type="integer">1</rating>
-
# <name>Microsoft</name>
-
# <address>
-
# ...
-
# </address>
-
# </client>
-
# </clients>
-
# <account>
-
# <id type="integer">1</id>
-
# <credit-limit type="integer">50</credit-limit>
-
# </account>
-
# </firm>
-
#
-
# To include any methods on the model being called use <tt>:methods</tt>:
-
#
-
# firm.to_xml methods: [ :calculated_earnings, :real_earnings ]
-
#
-
# <firm>
-
# # ... normal attributes as shown above ...
-
# <calculated-earnings>100000000000000000</calculated-earnings>
-
# <real-earnings>5</real-earnings>
-
# </firm>
-
#
-
# To call any additional Procs use <tt>:procs</tt>. The Procs are passed a
-
# modified version of the options hash that was given to +to_xml+:
-
#
-
# proc = Proc.new { |options| options[:builder].tag!('abc', 'def') }
-
# firm.to_xml procs: [ proc ]
-
#
-
# <firm>
-
# # ... normal attributes as shown above ...
-
# <abc>def</abc>
-
# </firm>
-
#
-
# Alternatively, you can yield the builder object as part of the +to_xml+ call:
-
#
-
# firm.to_xml do |xml|
-
# xml.creator do
-
# xml.first_name "David"
-
# xml.last_name "Heinemeier Hansson"
-
# end
-
# end
-
#
-
# <firm>
-
# # ... normal attributes as shown above ...
-
# <creator>
-
# <first_name>David</first_name>
-
# <last_name>Heinemeier Hansson</last_name>
-
# </creator>
-
# </firm>
-
#
-
# As noted above, you may override +to_xml+ in your ActiveRecord::Base
-
# subclasses to have complete control about what's generated. The general
-
# form of doing this is:
-
#
-
# class IHaveMyOwnXML < ActiveRecord::Base
-
# def to_xml(options = {})
-
# require 'builder'
-
# options[:indent] ||= 2
-
# xml = options[:builder] ||= ::Builder::XmlMarkup.new(indent: options[:indent])
-
# xml.instruct! unless options[:skip_instruct]
-
# xml.level_one do
-
# xml.tag!(:second_level, 'content')
-
# end
-
# end
-
# end
-
1
def to_xml(options = {}, &block)
-
XmlSerializer.new(self, options).serialize(&block)
-
end
-
end
-
-
1
class XmlSerializer < ActiveModel::Serializers::Xml::Serializer #:nodoc:
-
1
class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc:
-
1
def compute_type
-
klass = @serializable.class
-
column = klass.columns_hash[name] || Type::Value.new
-
-
type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name] || column.type
-
-
{ :text => :string,
-
:time => :datetime }[type] || type
-
end
-
1
protected :compute_type
-
end
-
end
-
end
-
1
module ActiveRecord
-
-
# Statement cache is used to cache a single statement in order to avoid creating the AST again.
-
# Initializing the cache is done by passing the statement in the create block:
-
#
-
# cache = StatementCache.create(Book.connection) do |params|
-
# Book.where(name: "my book").where("author_id > 3")
-
# end
-
#
-
# The cached statement is executed by using the +execute+ method:
-
#
-
# cache.execute([], Book, Book.connection)
-
#
-
# The relation returned by the block is cached, and for each +execute+ call the cached relation gets duped.
-
# Database is queried when +to_a+ is called on the relation.
-
#
-
# If you want to cache the statement without the values you can use the +bind+ method of the
-
# block parameter.
-
#
-
# cache = StatementCache.create(Book.connection) do |params|
-
# Book.where(name: params.bind)
-
# end
-
#
-
# And pass the bind values as the first argument of +execute+ call.
-
#
-
# cache.execute(["my book"], Book, Book.connection)
-
1
class StatementCache # :nodoc:
-
1
class Substitute; end # :nodoc:
-
-
1
class Query # :nodoc:
-
1
def initialize(sql)
-
4
@sql = sql
-
end
-
-
1
def sql_for(binds, connection)
-
24
@sql
-
end
-
end
-
-
1
class PartialQuery < Query # :nodoc:
-
1
def initialize values
-
@values = values
-
@indexes = values.each_with_index.find_all { |thing,i|
-
Arel::Nodes::BindParam === thing
-
}.map(&:last)
-
end
-
-
1
def sql_for(binds, connection)
-
val = @values.dup
-
binds = binds.dup
-
@indexes.each { |i| val[i] = connection.quote(*binds.shift.reverse) }
-
val.join
-
end
-
end
-
-
1
def self.query(visitor, ast)
-
4
Query.new visitor.accept(ast, Arel::Collectors::SQLString.new).value
-
end
-
-
1
def self.partial_query(visitor, ast, collector)
-
collected = visitor.accept(ast, collector).value
-
PartialQuery.new collected
-
end
-
-
1
class Params # :nodoc:
-
5
def bind; Substitute.new; end
-
end
-
-
1
class BindMap # :nodoc:
-
1
def initialize(bind_values)
-
4
@indexes = []
-
4
@bind_values = bind_values
-
-
4
bind_values.each_with_index do |(_, value), i|
-
4
if Substitute === value
-
4
@indexes << i
-
end
-
end
-
end
-
-
1
def bind(values)
-
48
bvs = @bind_values.map { |pair| pair.dup }
-
48
@indexes.each_with_index { |offset,i| bvs[offset][1] = values[i] }
-
24
bvs
-
end
-
end
-
-
1
attr_reader :bind_map, :query_builder
-
-
1
def self.create(connection, block = Proc.new)
-
4
relation = block.call Params.new
-
4
bind_map = BindMap.new relation.bind_values
-
4
query_builder = connection.cacheable_query relation.arel
-
4
new query_builder, bind_map
-
end
-
-
1
def initialize(query_builder, bind_map)
-
4
@query_builder = query_builder
-
4
@bind_map = bind_map
-
end
-
-
1
def execute(params, klass, connection)
-
24
bind_values = bind_map.bind params
-
-
24
sql = query_builder.sql_for bind_values, connection
-
-
24
klass.find_by_sql sql, bind_values
-
end
-
1
alias :call :execute
-
end
-
end
-
1
require 'active_support/core_ext/hash/indifferent_access'
-
-
1
module ActiveRecord
-
# Store gives you a thin wrapper around serialize for the purpose of storing hashes in a single column.
-
# It's like a simple key/value store baked into your record when you don't care about being able to
-
# query that store outside the context of a single record.
-
#
-
# You can then declare accessors to this store that are then accessible just like any other attribute
-
# of the model. This is very helpful for easily exposing store keys to a form or elsewhere that's
-
# already built around just accessing attributes on the model.
-
#
-
# Make sure that you declare the database column used for the serialized store as a text, so there's
-
# plenty of room.
-
#
-
# You can set custom coder to encode/decode your serialized attributes to/from different formats.
-
# JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+.
-
#
-
# NOTE - If you are using PostgreSQL specific columns like +hstore+ or +json+ there is no need for
-
# the serialization provided by +store+. Simply use +store_accessor+ instead to generate
-
# the accessor methods. Be aware that these columns use a string keyed hash and do not allow access
-
# using a symbol.
-
#
-
# Examples:
-
#
-
# class User < ActiveRecord::Base
-
# store :settings, accessors: [ :color, :homepage ], coder: JSON
-
# end
-
#
-
# u = User.new(color: 'black', homepage: '37signals.com')
-
# u.color # Accessor stored attribute
-
# u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
-
#
-
# # There is no difference between strings and symbols for accessing custom attributes
-
# u.settings[:country] # => 'Denmark'
-
# u.settings['country'] # => 'Denmark'
-
#
-
# # Add additional accessors to an existing store through store_accessor
-
# class SuperUser < User
-
# store_accessor :settings, :privileges, :servants
-
# end
-
#
-
# The stored attribute names can be retrieved using +stored_attributes+.
-
#
-
# User.stored_attributes[:settings] # [:color, :homepage]
-
#
-
# == Overwriting default accessors
-
#
-
# All stored values are automatically available through accessors on the Active Record
-
# object, but sometimes you want to specialize this behavior. This can be done by overwriting
-
# the default accessors (using the same name as the attribute) and calling <tt>super</tt>
-
# to actually change things.
-
#
-
# class Song < ActiveRecord::Base
-
# # Uses a stored integer to hold the volume adjustment of the song
-
# store :settings, accessors: [:volume_adjustment]
-
#
-
# def volume_adjustment=(decibels)
-
# super(decibels.to_i)
-
# end
-
#
-
# def volume_adjustment
-
# super.to_i
-
# end
-
# end
-
1
module Store
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
class << self
-
1
attr_accessor :local_stored_attributes
-
end
-
end
-
-
1
module ClassMethods
-
1
def store(store_attribute, options = {})
-
serialize store_attribute, IndifferentCoder.new(options[:coder])
-
store_accessor(store_attribute, options[:accessors]) if options.has_key? :accessors
-
end
-
-
1
def store_accessor(store_attribute, *keys)
-
keys = keys.flatten
-
-
_store_accessors_module.module_eval do
-
keys.each do |key|
-
define_method("#{key}=") do |value|
-
write_store_attribute(store_attribute, key, value)
-
end
-
-
define_method(key) do
-
read_store_attribute(store_attribute, key)
-
end
-
end
-
end
-
-
# assign new store attribute and create new hash to ensure that each class in the hierarchy
-
# has its own hash of stored attributes.
-
self.local_stored_attributes ||= {}
-
self.local_stored_attributes[store_attribute] ||= []
-
self.local_stored_attributes[store_attribute] |= keys
-
end
-
-
1
def _store_accessors_module # :nodoc:
-
@_store_accessors_module ||= begin
-
mod = Module.new
-
include mod
-
mod
-
end
-
end
-
-
1
def stored_attributes
-
parent = superclass.respond_to?(:stored_attributes) ? superclass.stored_attributes : {}
-
if self.local_stored_attributes
-
parent.merge!(self.local_stored_attributes) { |k, a, b| a | b }
-
end
-
parent
-
end
-
end
-
-
1
protected
-
1
def read_store_attribute(store_attribute, key)
-
accessor = store_accessor_for(store_attribute)
-
accessor.read(self, store_attribute, key)
-
end
-
-
1
def write_store_attribute(store_attribute, key, value)
-
accessor = store_accessor_for(store_attribute)
-
accessor.write(self, store_attribute, key, value)
-
end
-
-
1
private
-
1
def store_accessor_for(store_attribute)
-
type_for_attribute(store_attribute.to_s).accessor
-
end
-
-
1
class HashAccessor # :nodoc:
-
1
def self.read(object, attribute, key)
-
prepare(object, attribute)
-
object.public_send(attribute)[key]
-
end
-
-
1
def self.write(object, attribute, key, value)
-
prepare(object, attribute)
-
if value != read(object, attribute, key)
-
object.public_send :"#{attribute}_will_change!"
-
object.public_send(attribute)[key] = value
-
end
-
end
-
-
1
def self.prepare(object, attribute)
-
object.public_send :"#{attribute}=", {} unless object.send(attribute)
-
end
-
end
-
-
1
class StringKeyedHashAccessor < HashAccessor # :nodoc:
-
1
def self.read(object, attribute, key)
-
super object, attribute, key.to_s
-
end
-
-
1
def self.write(object, attribute, key, value)
-
super object, attribute, key.to_s, value
-
end
-
end
-
-
1
class IndifferentHashAccessor < ActiveRecord::Store::HashAccessor # :nodoc:
-
1
def self.prepare(object, store_attribute)
-
attribute = object.send(store_attribute)
-
unless attribute.is_a?(ActiveSupport::HashWithIndifferentAccess)
-
attribute = IndifferentCoder.as_indifferent_hash(attribute)
-
object.send :"#{store_attribute}=", attribute
-
end
-
attribute
-
end
-
end
-
-
1
class IndifferentCoder # :nodoc:
-
1
def initialize(coder_or_class_name)
-
@coder =
-
if coder_or_class_name.respond_to?(:load) && coder_or_class_name.respond_to?(:dump)
-
coder_or_class_name
-
else
-
ActiveRecord::Coders::YAMLColumn.new(coder_or_class_name || Object)
-
end
-
end
-
-
1
def dump(obj)
-
@coder.dump self.class.as_indifferent_hash(obj)
-
end
-
-
1
def load(yaml)
-
self.class.as_indifferent_hash(@coder.load(yaml || ''))
-
end
-
-
1
def self.as_indifferent_hash(obj)
-
case obj
-
when ActiveSupport::HashWithIndifferentAccess
-
obj
-
when Hash
-
obj.with_indifferent_access
-
else
-
ActiveSupport::HashWithIndifferentAccess.new
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
# = Active Record Timestamp
-
#
-
# Active Record automatically timestamps create and update operations if the
-
# table has fields named <tt>created_at/created_on</tt> or
-
# <tt>updated_at/updated_on</tt>.
-
#
-
# Timestamping can be turned off by setting:
-
#
-
# config.active_record.record_timestamps = false
-
#
-
# Timestamps are in UTC by default but you can use the local timezone by setting:
-
#
-
# config.active_record.default_timezone = :local
-
#
-
# == Time Zone aware attributes
-
#
-
# By default, ActiveRecord::Base keeps all the datetime columns time zone aware by executing following code.
-
#
-
# config.active_record.time_zone_aware_attributes = true
-
#
-
# This feature can easily be turned off by assigning value <tt>false</tt> .
-
#
-
# If your attributes are time zone aware and you desire to skip time zone conversion to the current Time.zone
-
# when reading certain attributes then you can do following:
-
#
-
# class Topic < ActiveRecord::Base
-
# self.skip_time_zone_conversion_for_attributes = [:written_on]
-
# end
-
1
module Timestamp
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
class_attribute :record_timestamps
-
1
self.record_timestamps = true
-
end
-
-
1
def initialize_dup(other) # :nodoc:
-
super
-
clear_timestamp_attributes
-
end
-
-
1
private
-
-
1
def _create_record
-
28
if self.record_timestamps
-
28
current_time = current_time_from_proper_timezone
-
-
28
all_timestamp_attributes.each do |column|
-
112
column = column.to_s
-
112
if has_attribute?(column) && !attribute_present?(column)
-
56
write_attribute(column, current_time)
-
end
-
end
-
end
-
-
28
super
-
end
-
-
1
def _update_record(*args)
-
3
if should_record_timestamps?
-
2
current_time = current_time_from_proper_timezone
-
-
2
timestamp_attributes_for_update_in_model.each do |column|
-
2
column = column.to_s
-
2
next if attribute_changed?(column)
-
2
write_attribute(column, current_time)
-
end
-
end
-
3
super
-
end
-
-
1
def should_record_timestamps?
-
3
self.record_timestamps && (!partial_writes? || changed?)
-
end
-
-
1
def timestamp_attributes_for_create_in_model
-
timestamp_attributes_for_create.select { |c| self.class.column_names.include?(c.to_s) }
-
end
-
-
1
def timestamp_attributes_for_update_in_model
-
6
timestamp_attributes_for_update.select { |c| self.class.column_names.include?(c.to_s) }
-
end
-
-
1
def all_timestamp_attributes_in_model
-
timestamp_attributes_for_create_in_model + timestamp_attributes_for_update_in_model
-
end
-
-
1
def timestamp_attributes_for_update
-
30
[:updated_at, :updated_on]
-
end
-
-
1
def timestamp_attributes_for_create
-
28
[:created_at, :created_on]
-
end
-
-
1
def all_timestamp_attributes
-
28
timestamp_attributes_for_create + timestamp_attributes_for_update
-
end
-
-
1
def max_updated_column_timestamp(timestamp_names = timestamp_attributes_for_update)
-
timestamp_names
-
.map { |attr| self[attr] }
-
.compact
-
.map(&:to_time)
-
.max
-
end
-
-
1
def current_time_from_proper_timezone
-
30
self.class.default_timezone == :utc ? Time.now.utc : Time.now
-
end
-
-
# Clear attributes and changed_attributes
-
1
def clear_timestamp_attributes
-
all_timestamp_attributes_in_model.each do |attribute_name|
-
self[attribute_name] = nil
-
clear_attribute_changes([attribute_name])
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
# See ActiveRecord::Transactions::ClassMethods for documentation.
-
1
module Transactions
-
1
extend ActiveSupport::Concern
-
#:nodoc:
-
1
ACTIONS = [:create, :destroy, :update]
-
#:nodoc:
-
1
CALLBACK_WARN_MESSAGE = "Currently, Active Record suppresses errors raised " \
-
"within `after_rollback`/`after_commit` callbacks and only print them to " \
-
"the logs. In the next version, these errors will no longer be suppressed. " \
-
"Instead, the errors will propagate normally just like in other Active " \
-
"Record callbacks.\n" \
-
"\n" \
-
"You can opt into the new behavior and remove this warning by setting:\n" \
-
"\n" \
-
" config.active_record.raise_in_transactional_callbacks = true\n\n"
-
-
1
included do
-
1
define_callbacks :commit, :rollback,
-
terminator: ->(_, result) { result == false },
-
scope: [:kind, :name]
-
-
1
mattr_accessor :raise_in_transactional_callbacks, instance_writer: false
-
1
self.raise_in_transactional_callbacks = false
-
end
-
-
# = Active Record Transactions
-
#
-
# Transactions are protective blocks where SQL statements are only permanent
-
# if they can all succeed as one atomic action. The classic example is a
-
# transfer between two accounts where you can only have a deposit if the
-
# withdrawal succeeded and vice versa. Transactions enforce the integrity of
-
# the database and guard the data against program errors or database
-
# break-downs. So basically you should use transaction blocks whenever you
-
# have a number of statements that must be executed together or not at all.
-
#
-
# For example:
-
#
-
# ActiveRecord::Base.transaction do
-
# david.withdrawal(100)
-
# mary.deposit(100)
-
# end
-
#
-
# This example will only take money from David and give it to Mary if neither
-
# +withdrawal+ nor +deposit+ raise an exception. Exceptions will force a
-
# ROLLBACK that returns the database to the state before the transaction
-
# began. Be aware, though, that the objects will _not_ have their instance
-
# data returned to their pre-transactional state.
-
#
-
# == Different Active Record classes in a single transaction
-
#
-
# Though the transaction class method is called on some Active Record class,
-
# the objects within the transaction block need not all be instances of
-
# that class. This is because transactions are per-database connection, not
-
# per-model.
-
#
-
# In this example a +balance+ record is transactionally saved even
-
# though +transaction+ is called on the +Account+ class:
-
#
-
# Account.transaction do
-
# balance.save!
-
# account.save!
-
# end
-
#
-
# The +transaction+ method is also available as a model instance method.
-
# For example, you can also do this:
-
#
-
# balance.transaction do
-
# balance.save!
-
# account.save!
-
# end
-
#
-
# == Transactions are not distributed across database connections
-
#
-
# A transaction acts on a single database connection. If you have
-
# multiple class-specific databases, the transaction will not protect
-
# interaction among them. One workaround is to begin a transaction
-
# on each class whose models you alter:
-
#
-
# Student.transaction do
-
# Course.transaction do
-
# course.enroll(student)
-
# student.units += course.units
-
# end
-
# end
-
#
-
# This is a poor solution, but fully distributed transactions are beyond
-
# the scope of Active Record.
-
#
-
# == +save+ and +destroy+ are automatically wrapped in a transaction
-
#
-
# Both +save+ and +destroy+ come wrapped in a transaction that ensures
-
# that whatever you do in validations or callbacks will happen under its
-
# protected cover. So you can use validations to check for values that
-
# the transaction depends on or you can raise exceptions in the callbacks
-
# to rollback, including <tt>after_*</tt> callbacks.
-
#
-
# As a consequence changes to the database are not seen outside your connection
-
# until the operation is complete. For example, if you try to update the index
-
# of a search engine in +after_save+ the indexer won't see the updated record.
-
# The +after_commit+ callback is the only one that is triggered once the update
-
# is committed. See below.
-
#
-
# == Exception handling and rolling back
-
#
-
# Also have in mind that exceptions thrown within a transaction block will
-
# be propagated (after triggering the ROLLBACK), so you should be ready to
-
# catch those in your application code.
-
#
-
# One exception is the <tt>ActiveRecord::Rollback</tt> exception, which will trigger
-
# a ROLLBACK when raised, but not be re-raised by the transaction block.
-
#
-
# *Warning*: one should not catch <tt>ActiveRecord::StatementInvalid</tt> exceptions
-
# inside a transaction block. <tt>ActiveRecord::StatementInvalid</tt> exceptions indicate that an
-
# error occurred at the database level, for example when a unique constraint
-
# is violated. On some database systems, such as PostgreSQL, database errors
-
# inside a transaction cause the entire transaction to become unusable
-
# until it's restarted from the beginning. Here is an example which
-
# demonstrates the problem:
-
#
-
# # Suppose that we have a Number model with a unique column called 'i'.
-
# Number.transaction do
-
# Number.create(i: 0)
-
# begin
-
# # This will raise a unique constraint error...
-
# Number.create(i: 0)
-
# rescue ActiveRecord::StatementInvalid
-
# # ...which we ignore.
-
# end
-
#
-
# # On PostgreSQL, the transaction is now unusable. The following
-
# # statement will cause a PostgreSQL error, even though the unique
-
# # constraint is no longer violated:
-
# Number.create(i: 1)
-
# # => "PGError: ERROR: current transaction is aborted, commands
-
# # ignored until end of transaction block"
-
# end
-
#
-
# One should restart the entire transaction if an
-
# <tt>ActiveRecord::StatementInvalid</tt> occurred.
-
#
-
# == Nested transactions
-
#
-
# +transaction+ calls can be nested. By default, this makes all database
-
# statements in the nested transaction block become part of the parent
-
# transaction. For example, the following behavior may be surprising:
-
#
-
# User.transaction do
-
# User.create(username: 'Kotori')
-
# User.transaction do
-
# User.create(username: 'Nemu')
-
# raise ActiveRecord::Rollback
-
# end
-
# end
-
#
-
# creates both "Kotori" and "Nemu". Reason is the <tt>ActiveRecord::Rollback</tt>
-
# exception in the nested block does not issue a ROLLBACK. Since these exceptions
-
# are captured in transaction blocks, the parent block does not see it and the
-
# real transaction is committed.
-
#
-
# In order to get a ROLLBACK for the nested transaction you may ask for a real
-
# sub-transaction by passing <tt>requires_new: true</tt>. If anything goes wrong,
-
# the database rolls back to the beginning of the sub-transaction without rolling
-
# back the parent transaction. If we add it to the previous example:
-
#
-
# User.transaction do
-
# User.create(username: 'Kotori')
-
# User.transaction(requires_new: true) do
-
# User.create(username: 'Nemu')
-
# raise ActiveRecord::Rollback
-
# end
-
# end
-
#
-
# only "Kotori" is created. This works on MySQL and PostgreSQL. SQLite3 version >= '3.6.8' also supports it.
-
#
-
# Most databases don't support true nested transactions. At the time of
-
# writing, the only database that we're aware of that supports true nested
-
# transactions, is MS-SQL. Because of this, Active Record emulates nested
-
# transactions by using savepoints on MySQL and PostgreSQL. See
-
# http://dev.mysql.com/doc/refman/5.6/en/savepoint.html
-
# for more information about savepoints.
-
#
-
# === Callbacks
-
#
-
# There are two types of callbacks associated with committing and rolling back transactions:
-
# +after_commit+ and +after_rollback+.
-
#
-
# +after_commit+ callbacks are called on every record saved or destroyed within a
-
# transaction immediately after the transaction is committed. +after_rollback+ callbacks
-
# are called on every record saved or destroyed within a transaction immediately after the
-
# transaction or savepoint is rolled back.
-
#
-
# These callbacks are useful for interacting with other systems since you will be guaranteed
-
# that the callback is only executed when the database is in a permanent state. For example,
-
# +after_commit+ is a good spot to put in a hook to clearing a cache since clearing it from
-
# within a transaction could trigger the cache to be regenerated before the database is updated.
-
#
-
# === Caveats
-
#
-
# If you're on MySQL, then do not use DDL operations in nested transactions
-
# blocks that are emulated with savepoints. That is, do not execute statements
-
# like 'CREATE TABLE' inside such blocks. This is because MySQL automatically
-
# releases all savepoints upon executing a DDL operation. When +transaction+
-
# is finished and tries to release the savepoint it created earlier, a
-
# database error will occur because the savepoint has already been
-
# automatically released. The following example demonstrates the problem:
-
#
-
# Model.connection.transaction do # BEGIN
-
# Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
-
# Model.connection.create_table(...) # active_record_1 now automatically released
-
# end # RELEASE savepoint active_record_1
-
# # ^^^^ BOOM! database error!
-
# end
-
#
-
# Note that "TRUNCATE" is also a MySQL DDL statement!
-
1
module ClassMethods
-
# See ActiveRecord::Transactions::ClassMethods for detailed documentation.
-
1
def transaction(options = {}, &block)
-
# See the ConnectionAdapters::DatabaseStatements#transaction API docs.
-
38
connection.transaction(options, &block)
-
end
-
-
# This callback is called after a record has been created, updated, or destroyed.
-
#
-
# You can specify that the callback should only be fired by a certain action with
-
# the +:on+ option:
-
#
-
# after_commit :do_foo, on: :create
-
# after_commit :do_bar, on: :update
-
# after_commit :do_baz, on: :destroy
-
#
-
# after_commit :do_foo_bar, on: [:create, :update]
-
# after_commit :do_bar_baz, on: [:update, :destroy]
-
#
-
# Note that transactional fixtures do not play well with this feature. Please
-
# use the +test_after_commit+ gem to have these hooks fired in tests.
-
1
def after_commit(*args, &block)
-
set_options_for_callbacks!(args)
-
set_callback(:commit, :after, *args, &block)
-
unless ActiveRecord::Base.raise_in_transactional_callbacks
-
ActiveSupport::Deprecation.warn(CALLBACK_WARN_MESSAGE)
-
end
-
end
-
-
# This callback is called after a create, update, or destroy are rolled back.
-
#
-
# Please check the documentation of +after_commit+ for options.
-
1
def after_rollback(*args, &block)
-
set_options_for_callbacks!(args)
-
set_callback(:rollback, :after, *args, &block)
-
unless ActiveRecord::Base.raise_in_transactional_callbacks
-
ActiveSupport::Deprecation.warn(CALLBACK_WARN_MESSAGE)
-
end
-
end
-
-
1
private
-
-
1
def set_options_for_callbacks!(args)
-
options = args.last
-
if options.is_a?(Hash) && options[:on]
-
fire_on = Array(options[:on])
-
assert_valid_transaction_action(fire_on)
-
options[:if] = Array(options[:if])
-
options[:if] << "transaction_include_any_action?(#{fire_on})"
-
end
-
end
-
-
1
def assert_valid_transaction_action(actions)
-
if (actions - ACTIONS).any?
-
raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS}"
-
end
-
end
-
end
-
-
# See ActiveRecord::Transactions::ClassMethods for detailed documentation.
-
1
def transaction(options = {}, &block)
-
self.class.transaction(options, &block)
-
end
-
-
1
def destroy #:nodoc:
-
2
with_transaction_returning_status { super }
-
end
-
-
1
def save(*) #:nodoc:
-
18
rollback_active_record_state! do
-
36
with_transaction_returning_status { super }
-
end
-
end
-
-
1
def save!(*) #:nodoc:
-
34
with_transaction_returning_status { super }
-
end
-
-
1
def touch(*) #:nodoc:
-
with_transaction_returning_status { super }
-
end
-
-
# Reset id and @new_record if the transaction rolls back.
-
1
def rollback_active_record_state!
-
18
remember_transaction_record_state
-
18
yield
-
rescue Exception
-
restore_transaction_record_state
-
raise
-
ensure
-
18
clear_transaction_record_state
-
end
-
-
# Call the +after_commit+ callbacks.
-
#
-
# Ensure that it is not called if the object was never persisted (failed create),
-
# but call it after the commit of a destroyed object.
-
1
def committed!(should_run_callbacks = true) #:nodoc:
-
_run_commit_callbacks if should_run_callbacks && destroyed? || persisted?
-
ensure
-
force_clear_transaction_record_state
-
end
-
-
# Call the +after_rollback+ callbacks. The +force_restore_state+ argument indicates if the record
-
# state should be rolled back to the beginning or just to the last savepoint.
-
1
def rolledback!(force_restore_state = false, should_run_callbacks = true) #:nodoc:
-
1
_run_rollback_callbacks if should_run_callbacks
-
ensure
-
1
restore_transaction_record_state(force_restore_state)
-
1
clear_transaction_record_state
-
end
-
-
# Add the record to the current transaction so that the +after_rollback+ and +after_commit+ callbacks
-
# can be called.
-
1
def add_to_transaction
-
38
if has_transactional_callbacks?
-
self.class.connection.add_transaction_record(self)
-
else
-
38
sync_with_transaction_state
-
38
set_transaction_state(self.class.connection.transaction_state)
-
end
-
38
remember_transaction_record_state
-
end
-
-
# Executes +method+ within a transaction and captures its return value as a
-
# status flag. If the status is true the transaction is committed, otherwise
-
# a ROLLBACK is issued. In any case the status flag is returned.
-
#
-
# This method is available within the context of an ActiveRecord::Base
-
# instance.
-
1
def with_transaction_returning_status
-
38
status = nil
-
38
self.class.transaction do
-
38
add_to_transaction
-
38
begin
-
38
status = yield
-
rescue ActiveRecord::Rollback
-
clear_transaction_record_state
-
status = nil
-
end
-
-
38
raise ActiveRecord::Rollback unless status
-
end
-
38
status
-
ensure
-
38
if @transaction_state && @transaction_state.committed?
-
32
clear_transaction_record_state
-
end
-
end
-
-
1
protected
-
-
# Save the new record state and id of a record so it can be restored later if a transaction fails.
-
1
def remember_transaction_record_state #:nodoc:
-
56
@_start_transaction_state[:id] = id
-
56
@_start_transaction_state.reverse_merge!(
-
new_record: @new_record,
-
destroyed: @destroyed,
-
frozen?: frozen?,
-
)
-
56
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
-
end
-
-
# Clear the new record state and id of a record.
-
1
def clear_transaction_record_state #:nodoc:
-
63
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
-
63
force_clear_transaction_record_state if @_start_transaction_state[:level] < 1
-
end
-
-
# Force to clear the transaction record state.
-
1
def force_clear_transaction_record_state #:nodoc:
-
43
@_start_transaction_state.clear
-
end
-
-
# Restore the new record state and id of a record that was previously saved by a call to save_record_state.
-
1
def restore_transaction_record_state(force = false) #:nodoc:
-
3
unless @_start_transaction_state.empty?
-
1
transaction_level = (@_start_transaction_state[:level] || 0) - 1
-
1
if transaction_level < 1 || force
-
1
restore_state = @_start_transaction_state
-
1
thaw
-
1
@new_record = restore_state[:new_record]
-
1
@destroyed = restore_state[:destroyed]
-
1
pk = self.class.primary_key
-
1
if pk && read_attribute(pk) != restore_state[:id]
-
write_attribute(pk, restore_state[:id])
-
end
-
1
freeze if restore_state[:frozen?]
-
end
-
end
-
end
-
-
# Determine if a record was created or destroyed in a transaction. State should be one of :new_record or :destroyed.
-
1
def transaction_record_state(state) #:nodoc:
-
@_start_transaction_state[state]
-
end
-
-
# Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks.
-
1
def transaction_include_any_action?(actions) #:nodoc:
-
actions.any? do |action|
-
case action
-
when :create
-
transaction_record_state(:new_record)
-
when :destroy
-
destroyed?
-
when :update
-
!(transaction_record_state(:new_record) || destroyed?)
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Translation
-
1
include ActiveModel::Translation
-
-
# Set the lookup ancestors for ActiveModel.
-
1
def lookup_ancestors #:nodoc:
-
50
klass = self
-
50
classes = [klass]
-
50
return classes if klass == ActiveRecord::Base
-
-
50
while klass != klass.base_class
-
classes << klass = klass.superclass
-
end
-
50
classes
-
end
-
-
# Set the i18n scope to overwrite ActiveModel.
-
1
def i18n_scope #:nodoc:
-
82
:activerecord
-
end
-
end
-
end
-
1
module ActiveRecord
-
# = Active Record RecordInvalid
-
#
-
# Raised by <tt>save!</tt> and <tt>create!</tt> when the record is invalid. Use the
-
# +record+ method to retrieve the record which did not validate.
-
#
-
# begin
-
# complex_operation_that_internally_calls_save!
-
# rescue ActiveRecord::RecordInvalid => invalid
-
# puts invalid.record.errors
-
# end
-
1
class RecordInvalid < ActiveRecordError
-
1
attr_reader :record
-
-
1
def initialize(record)
-
@record = record
-
errors = @record.errors.full_messages.join(", ")
-
super(I18n.t(:"#{@record.class.i18n_scope}.errors.messages.record_invalid", :errors => errors, :default => :"errors.messages.record_invalid"))
-
end
-
end
-
-
# = Active Record Validations
-
#
-
# Active Record includes the majority of its validations from <tt>ActiveModel::Validations</tt>
-
# all of which accept the <tt>:on</tt> argument to define the context where the
-
# validations are active. Active Record will always supply either the context of
-
# <tt>:create</tt> or <tt>:update</tt> dependent on whether the model is a
-
# <tt>new_record?</tt>.
-
1
module Validations
-
1
extend ActiveSupport::Concern
-
1
include ActiveModel::Validations
-
-
# The validation process on save can be skipped by passing <tt>validate: false</tt>.
-
# The regular Base#save method is replaced with this when the validations
-
# module is mixed in, which it is by default.
-
1
def save(options={})
-
18
perform_validations(options) ? super : false
-
end
-
-
# Attempts to save the record just like Base#save but will raise a +RecordInvalid+
-
# exception instead of returning +false+ if the record is not valid.
-
1
def save!(options={})
-
17
perform_validations(options) ? super : raise_record_invalid
-
end
-
-
# Runs all the validations within the specified context. Returns +true+ if
-
# no errors are found, +false+ otherwise.
-
#
-
# Aliased as validate.
-
#
-
# If the argument is +false+ (default is +nil+), the context is set to <tt>:create</tt> if
-
# <tt>new_record?</tt> is +true+, and to <tt>:update</tt> if it is not.
-
#
-
# Validations with no <tt>:on</tt> option will run no matter the context. Validations with
-
# some <tt>:on</tt> option will only run in the specified context.
-
1
def valid?(context = nil)
-
44
context ||= (new_record? ? :create : :update)
-
44
output = super(context)
-
44
errors.empty? && output
-
end
-
-
1
alias_method :validate, :valid?
-
-
# Runs all the validations within the specified context. Returns +true+ if
-
# no errors are found, raises +RecordInvalid+ otherwise.
-
#
-
# If the argument is +false+ (default is +nil+), the context is set to <tt>:create</tt> if
-
# <tt>new_record?</tt> is +true+, and to <tt>:update</tt> if it is not.
-
#
-
# Validations with no <tt>:on</tt> option will run no matter the context. Validations with
-
# some <tt>:on</tt> option will only run in the specified context.
-
1
def validate!(context = nil)
-
valid?(context) || raise_record_invalid
-
end
-
-
1
protected
-
-
1
def raise_record_invalid
-
raise(RecordInvalid.new(self))
-
end
-
-
1
def perform_validations(options={}) # :nodoc:
-
35
options[:validate] == false || valid?(options[:context])
-
end
-
end
-
end
-
-
1
require "active_record/validations/associated"
-
1
require "active_record/validations/uniqueness"
-
1
require "active_record/validations/presence"
-
1
module ActiveRecord
-
1
module Validations
-
1
class AssociatedValidator < ActiveModel::EachValidator #:nodoc:
-
1
def validate_each(record, attribute, value)
-
if Array.wrap(value).reject {|r| r.marked_for_destruction? || r.valid?}.any?
-
record.errors.add(attribute, :invalid, options.merge(:value => value))
-
end
-
end
-
end
-
-
1
module ClassMethods
-
# Validates whether the associated object or objects are all valid.
-
# Works with any kind of association.
-
#
-
# class Book < ActiveRecord::Base
-
# has_many :pages
-
# belongs_to :library
-
#
-
# validates_associated :pages, :library
-
# end
-
#
-
# WARNING: This validation must not be used on both ends of an association.
-
# Doing so will lead to a circular dependency and cause infinite recursion.
-
#
-
# NOTE: This validation will not fail if the association hasn't been
-
# assigned. If you want to ensure that the association is both present and
-
# guaranteed to be valid, you also need to use +validates_presence_of+.
-
#
-
# Configuration options:
-
#
-
# * <tt>:message</tt> - A custom error message (default is: "is invalid").
-
# * <tt>:on</tt> - Specifies the contexts where this validation is active.
-
# Runs in all validation contexts by default (nil). You can pass a symbol
-
# or an array of symbols. (e.g. <tt>on: :create</tt> or
-
# <tt>on: :custom_validation_context</tt> or
-
# <tt>on: [:create, :custom_validation_context]</tt>)
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
-
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
-
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
-
# proc or string should return or evaluate to a +true+ or +false+ value.
-
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
-
# determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
-
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
-
# method, proc or string should return or evaluate to a +true+ or +false+
-
# value.
-
1
def validates_associated(*attr_names)
-
validates_with AssociatedValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Validations
-
1
class PresenceValidator < ActiveModel::Validations::PresenceValidator # :nodoc:
-
1
def validate(record)
-
180
super
-
180
attributes.each do |attribute|
-
180
next unless record.class._reflect_on_association(attribute)
-
associated_records = Array.wrap(record.send(attribute))
-
-
# Superclass validates presence. Ensure present records aren't about to be destroyed.
-
if associated_records.present? && associated_records.all? { |r| r.marked_for_destruction? }
-
record.errors.add(attribute, :blank, options)
-
end
-
end
-
end
-
end
-
-
1
module ClassMethods
-
# Validates that the specified attributes are not blank (as defined by
-
# Object#blank?), and, if the attribute is an association, that the
-
# associated object is not marked for destruction. Happens by default
-
# on save.
-
#
-
# class Person < ActiveRecord::Base
-
# has_one :face
-
# validates_presence_of :face
-
# end
-
#
-
# The face attribute must be in the object and it cannot be blank or marked
-
# for destruction.
-
#
-
# If you want to validate the presence of a boolean field (where the real values
-
# are true and false), you will want to use
-
# <tt>validates_inclusion_of :field_name, in: [true, false]</tt>.
-
#
-
# This is due to the way Object#blank? handles boolean values:
-
# <tt>false.blank? # => true</tt>.
-
#
-
# This validator defers to the ActiveModel validation for presence, adding the
-
# check to see that an associated object is not marked for destruction. This
-
# prevents the parent object from validating successfully and saving, which then
-
# deletes the associated object, thus putting the parent object into an invalid
-
# state.
-
#
-
# Configuration options:
-
# * <tt>:message</tt> - A custom error message (default is: "can't be blank").
-
# * <tt>:on</tt> - Specifies the contexts where this validation is active.
-
# Runs in all validation contexts by default (nil). You can pass a symbol
-
# or an array of symbols. (e.g. <tt>on: :create</tt> or
-
# <tt>on: :custom_validation_context</tt> or
-
# <tt>on: [:create, :custom_validation_context]</tt>)
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if
-
# the validation should occur (e.g. <tt>if: :allow_validation</tt>, or
-
# <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc
-
# or string should return or evaluate to a +true+ or +false+ value.
-
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
-
# if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
-
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The method,
-
# proc or string should return or evaluate to a +true+ or +false+ value.
-
# * <tt>:strict</tt> - Specifies whether validation should be strict.
-
# See <tt>ActiveModel::Validation#validates!</tt> for more information.
-
1
def validates_presence_of(*attr_names)
-
validates_with PresenceValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Validations
-
1
class UniquenessValidator < ActiveModel::EachValidator # :nodoc:
-
1
def initialize(options)
-
1
if options[:conditions] && !options[:conditions].respond_to?(:call)
-
raise ArgumentError, "#{options[:conditions]} was passed as :conditions but is not callable. " \
-
"Pass a callable instead: `conditions: -> { where(approved: true) }`"
-
end
-
1
super({ case_sensitive: true }.merge!(options))
-
1
@klass = options[:class]
-
end
-
-
1
def validate_each(record, attribute, value)
-
36
finder_class = find_finder_class_for(record)
-
36
table = finder_class.arel_table
-
36
value = map_enum_attribute(finder_class, attribute, value)
-
-
36
begin
-
36
relation = build_relation(finder_class, table, attribute, value)
-
36
if record.persisted?
-
1
if finder_class.primary_key
-
1
relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.id))
-
else
-
raise UnknownPrimaryKey.new(finder_class, "Can not validate uniqueness for persisted record without primary key.")
-
end
-
end
-
36
relation = scope_relation(record, table, relation)
-
36
relation = finder_class.unscoped.where(relation)
-
36
relation = relation.merge(options[:conditions]) if options[:conditions]
-
rescue RangeError
-
relation = finder_class.none
-
end
-
-
36
if relation.exists?
-
2
error_options = options.except(:case_sensitive, :scope, :conditions)
-
2
error_options[:value] = value
-
-
2
record.errors.add(attribute, :taken, error_options)
-
end
-
end
-
-
1
protected
-
# The check for an existing value should be run from a class that
-
# isn't abstract. This means working down from the current class
-
# (self), to the first non-abstract class. Since classes don't know
-
# their subclasses, we have to build the hierarchy between self and
-
# the record's class.
-
1
def find_finder_class_for(record) #:nodoc:
-
36
class_hierarchy = [record.class]
-
-
36
while class_hierarchy.first != @klass
-
class_hierarchy.unshift(class_hierarchy.first.superclass)
-
end
-
-
72
class_hierarchy.detect { |klass| !klass.abstract_class? }
-
end
-
-
1
def build_relation(klass, table, attribute, value) #:nodoc:
-
36
if reflection = klass._reflect_on_association(attribute)
-
attribute = reflection.foreign_key
-
value = value.attributes[reflection.klass.primary_key] unless value.nil?
-
end
-
-
36
attribute_name = attribute.to_s
-
-
# the attribute may be an aliased attribute
-
36
if klass.attribute_aliases[attribute_name]
-
attribute = klass.attribute_aliases[attribute_name]
-
attribute_name = attribute.to_s
-
end
-
-
36
column = klass.columns_hash[attribute_name]
-
36
value = klass.connection.type_cast(value, column)
-
36
if value.is_a?(String) && column.limit
-
value = value.to_s[0, column.limit]
-
end
-
-
36
if !options[:case_sensitive] && value && column.text?
-
# will use SQL LOWER function before comparison, unless it detects a case insensitive collation
-
36
klass.connection.case_insensitive_comparison(table, attribute, column, value)
-
else
-
klass.connection.case_sensitive_comparison(table, attribute, column, value)
-
end
-
end
-
-
1
def scope_relation(record, table, relation)
-
36
Array(options[:scope]).each do |scope_item|
-
if reflection = record.class._reflect_on_association(scope_item)
-
scope_value = record.send(reflection.foreign_key)
-
scope_item = reflection.foreign_key
-
else
-
scope_value = record._read_attribute(scope_item)
-
end
-
relation = relation.and(table[scope_item].eq(scope_value))
-
end
-
-
36
relation
-
end
-
-
1
def map_enum_attribute(klass, attribute, value)
-
36
mapping = klass.defined_enums[attribute.to_s]
-
36
value = mapping[value] if value && mapping
-
36
value
-
end
-
end
-
-
1
module ClassMethods
-
# Validates whether the value of the specified attributes are unique
-
# across the system. Useful for making sure that only one user
-
# can be named "davidhh".
-
#
-
# class Person < ActiveRecord::Base
-
# validates_uniqueness_of :user_name
-
# end
-
#
-
# It can also validate whether the value of the specified attributes are
-
# unique based on a <tt>:scope</tt> parameter:
-
#
-
# class Person < ActiveRecord::Base
-
# validates_uniqueness_of :user_name, scope: :account_id
-
# end
-
#
-
# Or even multiple scope parameters. For example, making sure that a
-
# teacher can only be on the schedule once per semester for a particular
-
# class.
-
#
-
# class TeacherSchedule < ActiveRecord::Base
-
# validates_uniqueness_of :teacher_id, scope: [:semester_id, :class_id]
-
# end
-
#
-
# It is also possible to limit the uniqueness constraint to a set of
-
# records matching certain conditions. In this example archived articles
-
# are not being taken into consideration when validating uniqueness
-
# of the title attribute:
-
#
-
# class Article < ActiveRecord::Base
-
# validates_uniqueness_of :title, conditions: -> { where.not(status: 'archived') }
-
# end
-
#
-
# When the record is created, a check is performed to make sure that no
-
# record exists in the database with the given value for the specified
-
# attribute (that maps to a column). When the record is updated,
-
# the same check is made but disregarding the record itself.
-
#
-
# Configuration options:
-
#
-
# * <tt>:message</tt> - Specifies a custom error message (default is:
-
# "has already been taken").
-
# * <tt>:scope</tt> - One or more columns by which to limit the scope of
-
# the uniqueness constraint.
-
# * <tt>:conditions</tt> - Specify the conditions to be included as a
-
# <tt>WHERE</tt> SQL fragment to limit the uniqueness constraint lookup
-
# (e.g. <tt>conditions: -> { where(status: 'active') }</tt>).
-
# * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by
-
# non-text columns (+true+ by default).
-
# * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the
-
# attribute is +nil+ (default is +false+).
-
# * <tt>:allow_blank</tt> - If set to +true+, skips this validation if the
-
# attribute is blank (default is +false+).
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
-
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
-
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
-
# proc or string should return or evaluate to a +true+ or +false+ value.
-
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
-
# determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
-
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
-
# method, proc or string should return or evaluate to a +true+ or +false+
-
# value.
-
#
-
# === Concurrency and integrity
-
#
-
# Using this validation method in conjunction with ActiveRecord::Base#save
-
# does not guarantee the absence of duplicate record insertions, because
-
# uniqueness checks on the application level are inherently prone to race
-
# conditions. For example, suppose that two users try to post a Comment at
-
# the same time, and a Comment's title must be unique. At the database-level,
-
# the actions performed by these users could be interleaved in the following manner:
-
#
-
# User 1 | User 2
-
# ------------------------------------+--------------------------------------
-
# # User 1 checks whether there's |
-
# # already a comment with the title |
-
# # 'My Post'. This is not the case. |
-
# SELECT * FROM comments |
-
# WHERE title = 'My Post' |
-
# |
-
# | # User 2 does the same thing and also
-
# | # infers that their title is unique.
-
# | SELECT * FROM comments
-
# | WHERE title = 'My Post'
-
# |
-
# # User 1 inserts their comment. |
-
# INSERT INTO comments |
-
# (title, content) VALUES |
-
# ('My Post', 'hi!') |
-
# |
-
# | # User 2 does the same thing.
-
# | INSERT INTO comments
-
# | (title, content) VALUES
-
# | ('My Post', 'hello!')
-
# |
-
# | # ^^^^^^
-
# | # Boom! We now have a duplicate
-
# | # title!
-
#
-
# This could even happen if you use transactions with the 'serializable'
-
# isolation level. The best way to work around this problem is to add a unique
-
# index to the database table using
-
# ActiveRecord::ConnectionAdapters::SchemaStatements#add_index. In the
-
# rare case that a race condition occurs, the database will guarantee
-
# the field's uniqueness.
-
#
-
# When the database catches such a duplicate insertion,
-
# ActiveRecord::Base#save will raise an ActiveRecord::StatementInvalid
-
# exception. You can either choose to let this error propagate (which
-
# will result in the default Rails exception page being shown), or you
-
# can catch it and restart the transaction (e.g. by telling the user
-
# that the title already exists, and asking them to re-enter the title).
-
# This technique is also known as
-
# {optimistic concurrency control}[http://en.wikipedia.org/wiki/Optimistic_concurrency_control].
-
#
-
# The bundled ActiveRecord::ConnectionAdapters distinguish unique index
-
# constraint errors from other types of database errors by throwing an
-
# ActiveRecord::RecordNotUnique exception. For other adapters you will
-
# have to parse the (database-specific) exception message to detect such
-
# a case.
-
#
-
# The following bundled adapters throw the ActiveRecord::RecordNotUnique exception:
-
#
-
# * ActiveRecord::ConnectionAdapters::MysqlAdapter.
-
# * ActiveRecord::ConnectionAdapters::Mysql2Adapter.
-
# * ActiveRecord::ConnectionAdapters::SQLite3Adapter.
-
# * ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.
-
1
def validates_uniqueness_of(*attr_names)
-
validates_with UniquenessValidator, _merge_attributes(attr_names)
-
end
-
end
-
end
-
end
-
1
module ActiveSupport
-
# Backtraces often include many lines that are not relevant for the context
-
# under review. This makes it hard to find the signal amongst the backtrace
-
# noise, and adds debugging time. With a BacktraceCleaner, filters and
-
# silencers are used to remove the noisy lines, so that only the most relevant
-
# lines remain.
-
#
-
# Filters are used to modify lines of data, while silencers are used to remove
-
# lines entirely. The typical filter use case is to remove lengthy path
-
# information from the start of each line, and view file paths relevant to the
-
# app directory instead of the file system root. The typical silencer use case
-
# is to exclude the output of a noisy library from the backtrace, so that you
-
# can focus on the rest.
-
#
-
# bc = BacktraceCleaner.new
-
# bc.add_filter { |line| line.gsub(Rails.root.to_s, '') } # strip the Rails.root prefix
-
# bc.add_silencer { |line| line =~ /mongrel|rubygems/ } # skip any lines from mongrel or rubygems
-
# bc.clean(exception.backtrace) # perform the cleanup
-
#
-
# To reconfigure an existing BacktraceCleaner (like the default one in Rails)
-
# and show as much data as possible, you can always call
-
# <tt>BacktraceCleaner#remove_silencers!</tt>, which will restore the
-
# backtrace to a pristine state. If you need to reconfigure an existing
-
# BacktraceCleaner so that it does not filter or modify the paths of any lines
-
# of the backtrace, you can call <tt>BacktraceCleaner#remove_filters!</tt>
-
# These two methods will give you a completely untouched backtrace.
-
#
-
# Inspired by the Quiet Backtrace gem by Thoughtbot.
-
1
class BacktraceCleaner
-
1
def initialize
-
1
@filters, @silencers = [], []
-
end
-
-
# Returns the backtrace after all filters and silencers have been run
-
# against it. Filters run first, then silencers.
-
1
def clean(backtrace, kind = :silent)
-
filtered = filter_backtrace(backtrace)
-
-
case kind
-
when :silent
-
silence(filtered)
-
when :noise
-
noise(filtered)
-
else
-
filtered
-
end
-
end
-
1
alias :filter :clean
-
-
# Adds a filter from the block provided. Each line in the backtrace will be
-
# mapped against this filter.
-
#
-
# # Will turn "/my/rails/root/app/models/person.rb" into "/app/models/person.rb"
-
# backtrace_cleaner.add_filter { |line| line.gsub(Rails.root, '') }
-
1
def add_filter(&block)
-
4
@filters << block
-
end
-
-
# Adds a silencer from the block provided. If the silencer returns +true+
-
# for a given line, it will be excluded from the clean backtrace.
-
#
-
# # Will reject all lines that include the word "mongrel", like "/gems/mongrel/server.rb" or "/app/my_mongrel_server/rb"
-
# backtrace_cleaner.add_silencer { |line| line =~ /mongrel/ }
-
1
def add_silencer(&block)
-
1
@silencers << block
-
end
-
-
# Removes all silencers, but leaves in the filters. Useful if your
-
# context of debugging suddenly expands as you suspect a bug in one of
-
# the libraries you use.
-
1
def remove_silencers!
-
@silencers = []
-
end
-
-
# Removes all filters, but leaves in the silencers. Useful if you suddenly
-
# need to see entire filepaths in the backtrace that you had already
-
# filtered out.
-
1
def remove_filters!
-
@filters = []
-
end
-
-
1
private
-
1
def filter_backtrace(backtrace)
-
@filters.each do |f|
-
backtrace = backtrace.map { |line| f.call(line) }
-
end
-
-
backtrace
-
end
-
-
1
def silence(backtrace)
-
@silencers.each do |s|
-
backtrace = backtrace.reject { |line| s.call(line) }
-
end
-
-
backtrace
-
end
-
-
1
def noise(backtrace)
-
backtrace - silence(backtrace)
-
end
-
end
-
end
-
1
require 'securerandom'
-
-
1
module Digest
-
1
module UUID
-
1
DNS_NAMESPACE = "k\xA7\xB8\x10\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
-
1
URL_NAMESPACE = "k\xA7\xB8\x11\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
-
1
OID_NAMESPACE = "k\xA7\xB8\x12\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
-
1
X500_NAMESPACE = "k\xA7\xB8\x14\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
-
-
# Generates a v5 non-random UUID (Universally Unique IDentifier).
-
#
-
# Using Digest::MD5 generates version 3 UUIDs; Digest::SHA1 generates version 5 UUIDs.
-
# uuid_from_hash always generates the same UUID for a given name and namespace combination.
-
#
-
# See RFC 4122 for details of UUID at: http://www.ietf.org/rfc/rfc4122.txt
-
1
def self.uuid_from_hash(hash_class, uuid_namespace, name)
-
if hash_class == Digest::MD5
-
version = 3
-
elsif hash_class == Digest::SHA1
-
version = 5
-
else
-
raise ArgumentError, "Expected Digest::SHA1 or Digest::MD5, got #{hash_class.name}."
-
end
-
-
hash = hash_class.new
-
hash.update(uuid_namespace)
-
hash.update(name)
-
-
ary = hash.digest.unpack('NnnnnN')
-
ary[2] = (ary[2] & 0x0FFF) | (version << 12)
-
ary[3] = (ary[3] & 0x3FFF) | 0x8000
-
-
"%08x-%04x-%04x-%04x-%04x%08x" % ary
-
end
-
-
# Convenience method for uuid_from_hash using Digest::MD5.
-
1
def self.uuid_v3(uuid_namespace, name)
-
self.uuid_from_hash(Digest::MD5, uuid_namespace, name)
-
end
-
-
# Convenience method for uuid_from_hash using Digest::SHA1.
-
1
def self.uuid_v5(uuid_namespace, name)
-
self.uuid_from_hash(Digest::SHA1, uuid_namespace, name)
-
end
-
-
# Convenience method for SecureRandom.uuid.
-
1
def self.uuid_v4
-
SecureRandom.uuid
-
end
-
end
-
end
-
1
class Module
-
###
-
# TODO: remove this after 1.9 support is dropped
-
1
def methods_transplantable? # :nodoc:
-
2
x = Module.new {
-
2
def foo; end # :nodoc:
-
}
-
4
Module.new { define_method :bar, x.instance_method(:foo) }
-
2
true
-
rescue TypeError
-
false
-
end
-
end
-
# encoding: utf-8
-
1
require 'active_support/json'
-
1
require 'active_support/core_ext/string/access'
-
1
require 'active_support/core_ext/string/behavior'
-
1
require 'active_support/core_ext/module/delegation'
-
-
1
module ActiveSupport #:nodoc:
-
1
module Multibyte #:nodoc:
-
# Chars enables you to work transparently with UTF-8 encoding in the Ruby
-
# String class without having extensive knowledge about the encoding. A
-
# Chars object accepts a string upon initialization and proxies String
-
# methods in an encoding safe manner. All the normal String methods are also
-
# implemented on the proxy.
-
#
-
# String methods are proxied through the Chars object, and can be accessed
-
# through the +mb_chars+ method. Methods which would normally return a
-
# String object now return a Chars object so methods can be chained.
-
#
-
# 'The Perfect String '.mb_chars.downcase.strip.normalize # => "the perfect string"
-
#
-
# Chars objects are perfectly interchangeable with String objects as long as
-
# no explicit class checks are made. If certain methods do explicitly check
-
# the class, call +to_s+ before you pass chars objects to them.
-
#
-
# bad.explicit_checking_method 'T'.mb_chars.downcase.to_s
-
#
-
# The default Chars implementation assumes that the encoding of the string
-
# is UTF-8, if you want to handle different encodings you can write your own
-
# multibyte string handler and configure it through
-
# ActiveSupport::Multibyte.proxy_class.
-
#
-
# class CharsForUTF32
-
# def size
-
# @wrapped_string.size / 4
-
# end
-
#
-
# def self.accepts?(string)
-
# string.length % 4 == 0
-
# end
-
# end
-
#
-
# ActiveSupport::Multibyte.proxy_class = CharsForUTF32
-
1
class Chars
-
1
include Comparable
-
1
attr_reader :wrapped_string
-
1
alias to_s wrapped_string
-
1
alias to_str wrapped_string
-
-
1
delegate :<=>, :=~, :acts_like_string?, :to => :wrapped_string
-
-
# Creates a new Chars instance by wrapping _string_.
-
1
def initialize(string)
-
@wrapped_string = string
-
@wrapped_string.force_encoding(Encoding::UTF_8) unless @wrapped_string.frozen?
-
end
-
-
# Forward all undefined methods to the wrapped string.
-
1
def method_missing(method, *args, &block)
-
result = @wrapped_string.__send__(method, *args, &block)
-
if method.to_s =~ /!$/
-
self if result
-
else
-
result.kind_of?(String) ? chars(result) : result
-
end
-
end
-
-
# Returns +true+ if _obj_ responds to the given method. Private methods
-
# are included in the search only if the optional second parameter
-
# evaluates to +true+.
-
1
def respond_to_missing?(method, include_private)
-
@wrapped_string.respond_to?(method, include_private)
-
end
-
-
# Returns +true+ when the proxy class can handle the string. Returns
-
# +false+ otherwise.
-
1
def self.consumes?(string)
-
string.encoding == Encoding::UTF_8
-
end
-
-
# Works just like <tt>String#split</tt>, with the exception that the items
-
# in the resulting list are Chars instances instead of String. This makes
-
# chaining methods easier.
-
#
-
# 'Café périferôl'.mb_chars.split(/é/).map { |part| part.upcase.to_s } # => ["CAF", " P", "RIFERÔL"]
-
1
def split(*args)
-
@wrapped_string.split(*args).map { |i| self.class.new(i) }
-
end
-
-
# Works like <tt>String#slice!</tt>, but returns an instance of
-
# Chars, or nil if the string was not modified.
-
1
def slice!(*args)
-
chars(@wrapped_string.slice!(*args))
-
end
-
-
# Reverses all characters in the string.
-
#
-
# 'Café'.mb_chars.reverse.to_s # => 'éfaC'
-
1
def reverse
-
chars(Unicode.unpack_graphemes(@wrapped_string).reverse.flatten.pack('U*'))
-
end
-
-
# Limits the byte size of the string to a number of bytes without breaking
-
# characters. Usable when the storage for a string is limited for some
-
# reason.
-
#
-
# 'こんにちは'.mb_chars.limit(7).to_s # => "こん"
-
1
def limit(limit)
-
slice(0...translate_offset(limit))
-
end
-
-
# Converts characters in the string to uppercase.
-
#
-
# 'Laurent, où sont les tests ?'.mb_chars.upcase.to_s # => "LAURENT, OÙ SONT LES TESTS ?"
-
1
def upcase
-
chars Unicode.upcase(@wrapped_string)
-
end
-
-
# Converts characters in the string to lowercase.
-
#
-
# 'VĚDA A VÝZKUM'.mb_chars.downcase.to_s # => "věda a výzkum"
-
1
def downcase
-
chars Unicode.downcase(@wrapped_string)
-
end
-
-
# Converts characters in the string to the opposite case.
-
#
-
# 'El Cañón".mb_chars.swapcase.to_s # => "eL cAÑÓN"
-
1
def swapcase
-
chars Unicode.swapcase(@wrapped_string)
-
end
-
-
# Converts the first character to uppercase and the remainder to lowercase.
-
#
-
# 'über'.mb_chars.capitalize.to_s # => "Über"
-
1
def capitalize
-
(slice(0) || chars('')).upcase + (slice(1..-1) || chars('')).downcase
-
end
-
-
# Capitalizes the first letter of every word, when possible.
-
#
-
# "ÉL QUE SE ENTERÓ".mb_chars.titleize # => "Él Que Se Enteró"
-
# "日本語".mb_chars.titleize # => "日本語"
-
1
def titleize
-
chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.upcase($1)})
-
end
-
1
alias_method :titlecase, :titleize
-
-
# Returns the KC normalization of the string by default. NFKC is
-
# considered the best normalization form for passing strings to databases
-
# and validations.
-
#
-
# * <tt>form</tt> - The form you want to normalize in. Should be one of the following:
-
# <tt>:c</tt>, <tt>:kc</tt>, <tt>:d</tt>, or <tt>:kd</tt>. Default is
-
# ActiveSupport::Multibyte::Unicode.default_normalization_form
-
1
def normalize(form = nil)
-
chars(Unicode.normalize(@wrapped_string, form))
-
end
-
-
# Performs canonical decomposition on all the characters.
-
#
-
# 'é'.length # => 2
-
# 'é'.mb_chars.decompose.to_s.length # => 3
-
1
def decompose
-
chars(Unicode.decompose(:canonical, @wrapped_string.codepoints.to_a).pack('U*'))
-
end
-
-
# Performs composition on all the characters.
-
#
-
# 'é'.length # => 3
-
# 'é'.mb_chars.compose.to_s.length # => 2
-
1
def compose
-
chars(Unicode.compose(@wrapped_string.codepoints.to_a).pack('U*'))
-
end
-
-
# Returns the number of grapheme clusters in the string.
-
#
-
# 'क्षि'.mb_chars.length # => 4
-
# 'क्षि'.mb_chars.grapheme_length # => 3
-
1
def grapheme_length
-
Unicode.unpack_graphemes(@wrapped_string).length
-
end
-
-
# Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent
-
# resulting in a valid UTF-8 string.
-
#
-
# Passing +true+ will forcibly tidy all bytes, assuming that the string's
-
# encoding is entirely CP1252 or ISO-8859-1.
-
1
def tidy_bytes(force = false)
-
chars(Unicode.tidy_bytes(@wrapped_string, force))
-
end
-
-
1
def as_json(options = nil) #:nodoc:
-
to_s.as_json(options)
-
end
-
-
1
%w(capitalize downcase reverse tidy_bytes upcase).each do |method|
-
5
define_method("#{method}!") do |*args|
-
@wrapped_string = send(method, *args).to_s
-
self
-
end
-
end
-
-
1
protected
-
-
1
def translate_offset(byte_offset) #:nodoc:
-
return nil if byte_offset.nil?
-
return 0 if @wrapped_string == ''
-
-
begin
-
@wrapped_string.byteslice(0...byte_offset).unpack('U*').length
-
rescue ArgumentError
-
byte_offset -= 1
-
retry
-
end
-
end
-
-
1
def chars(string) #:nodoc:
-
self.class.new(string)
-
end
-
end
-
end
-
end
-
1
gem 'minitest' # make sure we get the gem, not stdlib
-
1
require 'minitest'
-
1
require 'active_support/testing/tagged_logging'
-
1
require 'active_support/testing/setup_and_teardown'
-
1
require 'active_support/testing/assertions'
-
1
require 'active_support/testing/deprecation'
-
1
require 'active_support/testing/declarative'
-
1
require 'active_support/testing/isolation'
-
1
require 'active_support/testing/constant_lookup'
-
1
require 'active_support/testing/time_helpers'
-
1
require 'active_support/core_ext/kernel/reporting'
-
1
require 'active_support/deprecation'
-
-
1
module ActiveSupport
-
1
class TestCase < ::Minitest::Test
-
1
Assertion = Minitest::Assertion
-
-
1
class << self
-
# Sets the order in which test cases are run.
-
#
-
# ActiveSupport::TestCase.test_order = :random # => :random
-
#
-
# Valid values are:
-
# * +:random+ (to run tests in random order)
-
# * +:parallel+ (to run tests in parallel)
-
# * +:sorted+ (to run tests alphabetically by method name)
-
# * +:alpha+ (equivalent to +:sorted+)
-
1
def test_order=(new_order)
-
ActiveSupport.test_order = new_order
-
end
-
-
# Returns the order in which test cases are run.
-
#
-
# ActiveSupport::TestCase.test_order # => :sorted
-
#
-
# Possible values are +:random+, +:parallel+, +:alpha+, +:sorted+.
-
# Defaults to +:sorted+.
-
1
def test_order
-
test_order = ActiveSupport.test_order
-
-
if test_order.nil?
-
ActiveSupport::Deprecation.warn "You did not specify a value for the " \
-
"configuration option `active_support.test_order`. In Rails 5, " \
-
"the default value of this option will change from `:sorted` to " \
-
"`:random`.\n" \
-
"To disable this warning and keep the current behavior, you can add " \
-
"the following line to your `config/environments/test.rb`:\n" \
-
"\n" \
-
" Rails.application.configure do\n" \
-
" config.active_support.test_order = :sorted\n" \
-
" end\n" \
-
"\n" \
-
"Alternatively, you can opt into the future behavior by setting this " \
-
"option to `:random`."
-
-
test_order = :sorted
-
self.test_order = test_order
-
end
-
-
test_order
-
end
-
-
1
alias :my_tests_are_order_dependent! :i_suck_and_my_tests_are_order_dependent!
-
end
-
-
1
alias_method :method_name, :name
-
-
1
include ActiveSupport::Testing::TaggedLogging
-
1
include ActiveSupport::Testing::SetupAndTeardown
-
1
include ActiveSupport::Testing::Assertions
-
1
include ActiveSupport::Testing::Deprecation
-
1
include ActiveSupport::Testing::TimeHelpers
-
1
extend ActiveSupport::Testing::Declarative
-
-
# test/unit backwards compatibility methods
-
1
alias :assert_raise :assert_raises
-
1
alias :assert_not_empty :refute_empty
-
1
alias :assert_not_equal :refute_equal
-
1
alias :assert_not_in_delta :refute_in_delta
-
1
alias :assert_not_in_epsilon :refute_in_epsilon
-
1
alias :assert_not_includes :refute_includes
-
1
alias :assert_not_instance_of :refute_instance_of
-
1
alias :assert_not_kind_of :refute_kind_of
-
1
alias :assert_no_match :refute_match
-
1
alias :assert_not_nil :refute_nil
-
1
alias :assert_not_operator :refute_operator
-
1
alias :assert_not_predicate :refute_predicate
-
1
alias :assert_not_respond_to :refute_respond_to
-
1
alias :assert_not_same :refute_same
-
-
# Fails if the block raises an exception.
-
#
-
# assert_nothing_raised do
-
# ...
-
# end
-
1
def assert_nothing_raised(*args)
-
yield
-
end
-
end
-
end
-
1
require 'active_support/core_ext/object/blank'
-
-
1
module ActiveSupport
-
1
module Testing
-
1
module Assertions
-
# Assert that an expression is not truthy. Passes if <tt>object</tt> is
-
# +nil+ or +false+. "Truthy" means "considered true in a conditional"
-
# like <tt>if foo</tt>.
-
#
-
# assert_not nil # => true
-
# assert_not false # => true
-
# assert_not 'foo' # => Expected "foo" to be nil or false
-
#
-
# An error message can be specified.
-
#
-
# assert_not foo, 'foo should be false'
-
1
def assert_not(object, message = nil)
-
message ||= "Expected #{mu_pp(object)} to be nil or false"
-
assert !object, message
-
end
-
-
# Test numeric difference between the return value of an expression as a
-
# result of what is evaluated in the yielded block.
-
#
-
# assert_difference 'Article.count' do
-
# post :create, article: {...}
-
# end
-
#
-
# An arbitrary expression is passed in and evaluated.
-
#
-
# assert_difference 'assigns(:article).comments(:reload).size' do
-
# post :create, comment: {...}
-
# end
-
#
-
# An arbitrary positive or negative difference can be specified.
-
# The default is <tt>1</tt>.
-
#
-
# assert_difference 'Article.count', -1 do
-
# post :delete, id: ...
-
# end
-
#
-
# An array of expressions can also be passed in and evaluated.
-
#
-
# assert_difference [ 'Article.count', 'Post.count' ], 2 do
-
# post :create, article: {...}
-
# end
-
#
-
# A lambda or a list of lambdas can be passed in and evaluated:
-
#
-
# assert_difference ->{ Article.count }, 2 do
-
# post :create, article: {...}
-
# end
-
#
-
# assert_difference [->{ Article.count }, ->{ Post.count }], 2 do
-
# post :create, article: {...}
-
# end
-
#
-
# An error message can be specified.
-
#
-
# assert_difference 'Article.count', -1, 'An Article should be destroyed' do
-
# post :delete, id: ...
-
# end
-
1
def assert_difference(expression, difference = 1, message = nil, &block)
-
expressions = Array(expression)
-
-
exps = expressions.map { |e|
-
e.respond_to?(:call) ? e : lambda { eval(e, block.binding) }
-
}
-
before = exps.map { |e| e.call }
-
-
yield
-
-
expressions.zip(exps).each_with_index do |(code, e), i|
-
error = "#{code.inspect} didn't change by #{difference}"
-
error = "#{message}.\n#{error}" if message
-
assert_equal(before[i] + difference, e.call, error)
-
end
-
end
-
-
# Assertion that the numeric result of evaluating an expression is not
-
# changed before and after invoking the passed in block.
-
#
-
# assert_no_difference 'Article.count' do
-
# post :create, article: invalid_attributes
-
# end
-
#
-
# An error message can be specified.
-
#
-
# assert_no_difference 'Article.count', 'An Article should not be created' do
-
# post :create, article: invalid_attributes
-
# end
-
1
def assert_no_difference(expression, message = nil, &block)
-
assert_difference expression, 0, message, &block
-
end
-
end
-
end
-
end
-
1
require "active_support/concern"
-
1
require "active_support/inflector"
-
-
1
module ActiveSupport
-
1
module Testing
-
# Resolves a constant from a minitest spec name.
-
#
-
# Given the following spec-style test:
-
#
-
# describe WidgetsController, :index do
-
# describe "authenticated user" do
-
# describe "returns widgets" do
-
# it "has a controller that exists" do
-
# assert_kind_of WidgetsController, @controller
-
# end
-
# end
-
# end
-
# end
-
#
-
# The test will have the following name:
-
#
-
# "WidgetsController::index::authenticated user::returns widgets"
-
#
-
# The constant WidgetsController can be resolved from the name.
-
# The following code will resolve the constant:
-
#
-
# controller = determine_constant_from_test_name(name) do |constant|
-
# Class === constant && constant < ::ActionController::Metal
-
# end
-
1
module ConstantLookup
-
1
extend ::ActiveSupport::Concern
-
-
1
module ClassMethods # :nodoc:
-
1
def determine_constant_from_test_name(test_name)
-
names = test_name.split "::"
-
while names.size > 0 do
-
names.last.sub!(/Test$/, "")
-
begin
-
constant = names.join("::").safe_constantize
-
break(constant) if yield(constant)
-
ensure
-
names.pop
-
end
-
end
-
end
-
end
-
-
end
-
end
-
end
-
1
module ActiveSupport
-
1
module Testing
-
1
module Declarative
-
1
unless defined?(Spec)
-
# Helper to define a test method using a String. Under the hood, it replaces
-
# spaces with underscores and defines the test method.
-
#
-
# test "verify something" do
-
# ...
-
# end
-
1
def test(name, &block)
-
test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym
-
defined = method_defined? test_name
-
raise "#{test_name} is already defined in #{self}" if defined
-
if block_given?
-
define_method(test_name, &block)
-
else
-
define_method(test_name) do
-
flunk "No implementation provided for #{name}"
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/deprecation'
-
-
1
module ActiveSupport
-
1
module Testing
-
1
module Deprecation #:nodoc:
-
1
def assert_deprecated(match = nil, &block)
-
result, warnings = collect_deprecations(&block)
-
assert !warnings.empty?, "Expected a deprecation warning within the block but received none"
-
if match
-
match = Regexp.new(Regexp.escape(match)) unless match.is_a?(Regexp)
-
assert warnings.any? { |w| w =~ match }, "No deprecation warning matched #{match}: #{warnings.join(', ')}"
-
end
-
result
-
end
-
-
1
def assert_not_deprecated(&block)
-
result, deprecations = collect_deprecations(&block)
-
assert deprecations.empty?, "Expected no deprecation warning within the block but received #{deprecations.size}: \n #{deprecations * "\n "}"
-
result
-
end
-
-
1
def collect_deprecations
-
old_behavior = ActiveSupport::Deprecation.behavior
-
deprecations = []
-
ActiveSupport::Deprecation.behavior = Proc.new do |message, callstack|
-
deprecations << message
-
end
-
result = yield
-
[result, deprecations]
-
ensure
-
ActiveSupport::Deprecation.behavior = old_behavior
-
end
-
end
-
end
-
end
-
1
require 'rbconfig'
-
-
1
module ActiveSupport
-
1
module Testing
-
1
module Isolation
-
1
require 'thread'
-
-
1
def self.included(klass) #:nodoc:
-
klass.class_eval do
-
parallelize_me!
-
end
-
end
-
-
1
def self.forking_env?
-
1
!ENV["NO_FORK"] && ((RbConfig::CONFIG['host_os'] !~ /mswin|mingw/) && (RUBY_PLATFORM !~ /java/))
-
end
-
-
1
@@class_setup_mutex = Mutex.new
-
-
1
def _run_class_setup # class setup method should only happen in parent
-
@@class_setup_mutex.synchronize do
-
unless defined?(@@ran_class_setup) || ENV['ISOLATION_TEST']
-
self.class.setup if self.class.respond_to?(:setup)
-
@@ran_class_setup = true
-
end
-
end
-
end
-
-
1
def run
-
serialized = run_in_isolation do
-
super
-
end
-
-
Marshal.load(serialized)
-
end
-
-
1
module Forking
-
1
def run_in_isolation(&blk)
-
read, write = IO.pipe
-
read.binmode
-
write.binmode
-
-
pid = fork do
-
read.close
-
yield
-
write.puts [Marshal.dump(self.dup)].pack("m")
-
exit!
-
end
-
-
write.close
-
result = read.read
-
Process.wait2(pid)
-
return result.unpack("m")[0]
-
end
-
end
-
-
1
module Subprocess
-
1
ORIG_ARGV = ARGV.dup unless defined?(ORIG_ARGV)
-
-
# Crazy H4X to get this working in windows / jruby with
-
# no forking.
-
1
def run_in_isolation(&blk)
-
require "tempfile"
-
-
if ENV["ISOLATION_TEST"]
-
yield
-
File.open(ENV["ISOLATION_OUTPUT"], "w") do |file|
-
file.puts [Marshal.dump(self.dup)].pack("m")
-
end
-
exit!
-
else
-
Tempfile.open("isolation") do |tmpfile|
-
env = {
-
ISOLATION_TEST: self.class.name,
-
ISOLATION_OUTPUT: tmpfile.path
-
}
-
-
load_paths = $-I.map {|p| "-I\"#{File.expand_path(p)}\"" }.join(" ")
-
orig_args = ORIG_ARGV.join(" ")
-
test_opts = "-n#{self.class.name}##{self.name}"
-
command = "#{Gem.ruby} #{load_paths} #{$0} #{orig_args} #{test_opts}"
-
-
# IO.popen lets us pass env in a cross-platform way
-
child = IO.popen([env, command])
-
-
begin
-
Process.wait(child.pid)
-
rescue Errno::ECHILD # The child process may exit before we wait
-
nil
-
end
-
-
return tmpfile.read.unpack("m")[0]
-
end
-
end
-
end
-
end
-
-
1
include forking_env? ? Forking : Subprocess
-
end
-
end
-
end
-
1
require 'active_support/concern'
-
1
require 'active_support/callbacks'
-
-
1
module ActiveSupport
-
1
module Testing
-
# Adds support for +setup+ and +teardown+ callbacks.
-
# These callbacks serve as a replacement to overwriting the
-
# <tt>#setup</tt> and <tt>#teardown</tt> methods of your TestCase.
-
#
-
# class ExampleTest < ActiveSupport::TestCase
-
# setup do
-
# # ...
-
# end
-
#
-
# teardown do
-
# # ...
-
# end
-
# end
-
1
module SetupAndTeardown
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
include ActiveSupport::Callbacks
-
1
define_callbacks :setup, :teardown
-
end
-
-
1
module ClassMethods
-
# Add a callback, which runs before <tt>TestCase#setup</tt>.
-
1
def setup(*args, &block)
-
7
set_callback(:setup, :before, *args, &block)
-
end
-
-
# Add a callback, which runs after <tt>TestCase#teardown</tt>.
-
1
def teardown(*args, &block)
-
4
set_callback(:teardown, :after, *args, &block)
-
end
-
end
-
-
1
def before_setup # :nodoc:
-
super
-
run_callbacks :setup
-
end
-
-
1
def after_teardown # :nodoc:
-
run_callbacks :teardown
-
super
-
end
-
end
-
end
-
end
-
1
module ActiveSupport
-
1
module Testing
-
# Logs a "PostsControllerTest: test name" heading before each test to
-
# make test.log easier to search and follow along with.
-
1
module TaggedLogging #:nodoc:
-
1
attr_writer :tagged_logger
-
-
1
def before_setup
-
if tagged_logger && tagged_logger.info?
-
heading = "#{self.class}: #{name}"
-
divider = '-' * heading.size
-
tagged_logger.info divider
-
tagged_logger.info heading
-
tagged_logger.info divider
-
end
-
super
-
end
-
-
1
private
-
1
def tagged_logger
-
@tagged_logger ||= (defined?(Rails.logger) && Rails.logger)
-
end
-
end
-
end
-
end
-
1
module ActiveSupport
-
1
module Testing
-
1
class SimpleStubs # :nodoc:
-
1
Stub = Struct.new(:object, :method_name, :original_method)
-
-
1
def initialize
-
@stubs = {}
-
end
-
-
1
def stub_object(object, method_name, return_value)
-
key = [object.object_id, method_name]
-
-
if stub = @stubs[key]
-
unstub_object(stub)
-
end
-
-
new_name = "__simple_stub__#{method_name}"
-
-
@stubs[key] = Stub.new(object, method_name, new_name)
-
-
object.singleton_class.send :alias_method, new_name, method_name
-
object.define_singleton_method(method_name) { return_value }
-
end
-
-
1
def unstub_all!
-
@stubs.each_value do |stub|
-
unstub_object(stub)
-
end
-
@stubs = {}
-
end
-
-
1
private
-
-
1
def unstub_object(stub)
-
singleton_class = stub.object.singleton_class
-
singleton_class.send :undef_method, stub.method_name
-
singleton_class.send :alias_method, stub.method_name, stub.original_method
-
singleton_class.send :undef_method, stub.original_method
-
end
-
end
-
-
# Containing helpers that helps you test passage of time.
-
1
module TimeHelpers
-
# Changes current time to the time in the future or in the past by a given time difference by
-
# stubbing +Time.now+ and +Date.today+.
-
#
-
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
-
# travel 1.day
-
# Time.current # => Sun, 10 Nov 2013 15:34:49 EST -05:00
-
# Date.current # => Sun, 10 Nov 2013
-
#
-
# This method also accepts a block, which will return the current time back to its original
-
# state at the end of the block:
-
#
-
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
-
# travel 1.day do
-
# User.create.created_at # => Sun, 10 Nov 2013 15:34:49 EST -05:00
-
# end
-
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
-
1
def travel(duration, &block)
-
travel_to Time.now + duration, &block
-
end
-
-
# Changes current time to the given time by stubbing +Time.now+ and
-
# +Date.today+ to return the time or date passed into this method.
-
#
-
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
-
# travel_to Time.new(2004, 11, 24, 01, 04, 44)
-
# Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
-
# Date.current # => Wed, 24 Nov 2004
-
#
-
# Dates are taken as their timestamp at the beginning of the day in the
-
# application time zone. <tt>Time.current</tt> returns said timestamp,
-
# and <tt>Time.now</tt> its equivalent in the system time zone. Similarly,
-
# <tt>Date.current</tt> returns a date equal to the argument, and
-
# <tt>Date.today</tt> the date according to <tt>Time.now</tt>, which may
-
# be different. (Note that you rarely want to deal with <tt>Time.now</tt>,
-
# or <tt>Date.today</tt>, in order to honor the application time zone
-
# please always use <tt>Time.current</tt> and <tt>Date.current</tt>.)
-
#
-
# Note that the usec for the time passed will be set to 0 to prevent rounding
-
# errors with external services, like MySQL (which will round instead of floor,
-
# leading to off-by-one-second errors).
-
#
-
# This method also accepts a block, which will return the current time back to its original
-
# state at the end of the block:
-
#
-
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
-
# travel_to Time.new(2004, 11, 24, 01, 04, 44) do
-
# Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
-
# end
-
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
-
1
def travel_to(date_or_time)
-
if date_or_time.is_a?(Date) && !date_or_time.is_a?(DateTime)
-
now = date_or_time.midnight.to_time
-
else
-
now = date_or_time.to_time.change(usec: 0)
-
end
-
-
simple_stubs.stub_object(Time, :now, now)
-
simple_stubs.stub_object(Date, :today, now.to_date)
-
-
if block_given?
-
begin
-
yield
-
ensure
-
travel_back
-
end
-
end
-
end
-
-
# Returns the current time back to its original state, by removing the stubs added by
-
# `travel` and `travel_to`.
-
#
-
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
-
# travel_to Time.new(2004, 11, 24, 01, 04, 44)
-
# Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
-
# travel_back
-
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
-
1
def travel_back
-
simple_stubs.unstub_all!
-
end
-
-
1
private
-
-
1
def simple_stubs
-
@simple_stubs ||= SimpleStubs.new
-
end
-
end
-
end
-
end
-
1
module Arel
-
1
module Visitors
-
1
module BindVisitor
-
1
def initialize target
-
@block = nil
-
super
-
end
-
-
1
def accept node, collector, &block
-
@block = block if block_given?
-
super
-
end
-
-
1
private
-
-
1
def visit_Arel_Nodes_Assignment o, collector
-
if o.right.is_a? Arel::Nodes::BindParam
-
collector = visit o.left, collector
-
collector << " = "
-
visit o.right, collector
-
else
-
super
-
end
-
end
-
-
1
def visit_Arel_Nodes_BindParam o, collector
-
if @block
-
val = @block.call
-
if String === val
-
collector << val
-
end
-
else
-
super
-
end
-
end
-
-
end
-
end
-
end
-
1
require 'capybara'
-
-
1
module Capybara
-
1
module DSL
-
1
def self.included(base)
-
warn "including Capybara::DSL in the global scope is not recommended!" if base == Object
-
super
-
end
-
-
1
def self.extended(base)
-
1
warn "extending the main object with Capybara::DSL is not recommended!" if base == TOPLEVEL_BINDING.eval("self")
-
1
super
-
end
-
-
##
-
#
-
# Shortcut to working in a different session.
-
#
-
1
def using_session(name, &block)
-
Capybara.using_session(name, &block)
-
end
-
-
##
-
#
-
# Shortcut to using a different wait time.
-
#
-
1
def using_wait_time(seconds, &block)
-
Capybara.using_wait_time(seconds, &block)
-
end
-
-
##
-
#
-
# Shortcut to accessing the current session.
-
#
-
# class MyClass
-
# include Capybara::DSL
-
#
-
# def has_header?
-
# page.has_css?('h1')
-
# end
-
# end
-
#
-
# @return [Capybara::Session] The current session object
-
#
-
1
def page
-
Capybara.current_session
-
end
-
-
1
Session::DSL_METHODS.each do |method|
-
95
define_method method do |*args, &block|
-
page.send method, *args, &block
-
end
-
end
-
end
-
-
1
extend(Capybara::DSL)
-
end
-
1
require 'capybara/dsl'
-
-
1
Capybara.app = Rack::Builder.new do
-
1
map "/" do
-
1
if Gem::Version.new(Rails.version) >= Gem::Version.new("3.0")
-
1
run Rails.application
-
else # Rails 2
-
use Rails::Rack::Static
-
run ActionController::Dispatcher.new
-
end
-
end
-
end.to_app
-
-
1
Capybara.save_and_open_page_path = Rails.root.join('tmp/capybara')
-
-
# Override default rack_test driver to respect data-method attributes.
-
1
Capybara.register_driver :rack_test do |app|
-
Capybara::RackTest::Driver.new(app, :respect_data_method => true)
-
end
-
1
require 'capybara/dsl'
-
1
require 'rspec/core'
-
1
require 'capybara/rspec/matchers'
-
1
require 'capybara/rspec/features'
-
-
1
RSpec.configure do |config|
-
1
config.include Capybara::DSL, :type => :feature
-
1
config.include Capybara::RSpecMatchers, :type => :feature
-
1
config.include Capybara::RSpecMatchers, :type => :view
-
-
# A work-around to support accessing the current example that works in both
-
# RSpec 2 and RSpec 3.
-
1
fetch_current_example = RSpec.respond_to?(:current_example) ?
-
proc { RSpec.current_example } : proc { |context| context.example }
-
-
# The before and after blocks must run instantaneously, because Capybara
-
# might not actually be used in all examples where it's included.
-
1
config.after do
-
28
if self.class.include?(Capybara::DSL)
-
Capybara.reset_sessions!
-
Capybara.use_default_driver
-
end
-
end
-
1
config.before do
-
28
if self.class.include?(Capybara::DSL)
-
example = fetch_current_example.call(self)
-
Capybara.current_driver = Capybara.javascript_driver if example.metadata[:js]
-
Capybara.current_driver = example.metadata[:driver] if example.metadata[:driver]
-
end
-
end
-
end
-
-
1
if RSpec::Core::Version::STRING.to_f >= 3.0
-
1
RSpec.shared_context "Capybara Features", :capybara_feature => true do
-
instance_eval do
-
alias background before
-
alias given let
-
alias given! let!
-
end
-
end
-
-
1
RSpec.configure do |config|
-
1
config.alias_example_group_to :feature, :capybara_feature => true, :type => :feature
-
1
config.alias_example_group_to :xfeature, :capybara_feature => true, :type => :feature, :skip => "Temporarily disabled with xfeature"
-
1
config.alias_example_group_to :ffeature, :capybara_feature => true, :type => :feature, :focus => true
-
1
config.alias_example_to :scenario
-
1
config.alias_example_to :xscenario, :skip => "Temporarily disabled with xscenario"
-
1
config.alias_example_to :fscenario, :focus => true
-
end
-
else
-
module Capybara
-
module Features
-
def self.included(base)
-
base.instance_eval do
-
alias :background :before
-
alias :scenario :it
-
alias :xscenario :xit
-
alias :given :let
-
alias :given! :let!
-
alias :feature :describe
-
end
-
end
-
end
-
end
-
-
-
def self.feature(*args, &block)
-
options = if args.last.is_a?(Hash) then args.pop else {} end
-
options[:capybara_feature] = true
-
options[:type] = :feature
-
options[:caller] ||= caller
-
args.push(options)
-
-
#call describe on RSpec in case user has expose_dsl_globally set to false
-
RSpec.describe(*args, &block)
-
end
-
-
RSpec.configuration.include Capybara::Features, :capybara_feature => true
-
end
-
1
module Capybara
-
1
module RSpecMatchers
-
1
class Matcher
-
1
include ::RSpec::Matchers::Composable if defined?(::RSpec::Expectations::Version) && RSpec::Expectations::Version::STRING.to_f >= 3.0
-
-
1
def wrap(actual)
-
if actual.respond_to?("has_selector?")
-
actual
-
else
-
Capybara.string(actual.to_s)
-
end
-
end
-
end
-
-
1
class HaveSelector < Matcher
-
1
attr_reader :failure_message, :failure_message_when_negated
-
-
1
def initialize(*args)
-
@args = args
-
end
-
-
1
def matches?(actual)
-
wrap(actual).assert_selector(*@args)
-
rescue Capybara::ExpectationNotMet => e
-
@failure_message = e.message
-
return false
-
end
-
-
1
def does_not_match?(actual)
-
wrap(actual).assert_no_selector(*@args)
-
rescue Capybara::ExpectationNotMet => e
-
@failure_message_when_negated = e.message
-
return false
-
end
-
-
1
def description
-
"have #{query.description}"
-
end
-
-
1
def query
-
@query ||= Capybara::Query.new(*@args)
-
end
-
-
# RSpec 2 compatibility:
-
1
alias_method :failure_message_for_should, :failure_message
-
1
alias_method :failure_message_for_should_not, :failure_message_when_negated
-
end
-
-
1
class HaveText < Matcher
-
1
attr_reader :type, :content, :options
-
-
1
attr_reader :failure_message, :failure_message_when_negated
-
-
1
def initialize(*args)
-
@args = args.dup
-
-
# are set just for backwards compatability
-
@type = args.shift if args.first.is_a?(Symbol)
-
@content = args.shift
-
@options = (args.first.is_a?(Hash))? args.first : {}
-
end
-
-
1
def matches?(actual)
-
wrap(actual).assert_text(*@args)
-
rescue Capybara::ExpectationNotMet => e
-
@failure_message = e.message
-
return false
-
end
-
-
1
def does_not_match?(actual)
-
wrap(actual).assert_no_text(*@args)
-
rescue Capybara::ExpectationNotMet => e
-
@failure_message_when_negated = e.message
-
return false
-
end
-
-
1
def description
-
"text #{format(content)}"
-
end
-
-
1
def format(content)
-
content = Capybara::Helpers.normalize_whitespace(content) unless content.is_a? Regexp
-
content.inspect
-
end
-
-
# RSpec 2 compatibility:
-
1
alias_method :failure_message_for_should, :failure_message
-
1
alias_method :failure_message_for_should_not, :failure_message_when_negated
-
end
-
-
1
class HaveTitle < Matcher
-
1
attr_reader :title
-
-
1
attr_reader :failure_message, :failure_message_when_negated
-
-
1
def initialize(*args)
-
@args = args
-
-
# are set just for backwards compatability
-
@title = args.first
-
end
-
-
1
def matches?(actual)
-
wrap(actual).assert_title(*@args)
-
rescue Capybara::ExpectationNotMet => e
-
@failure_message = e.message
-
return false
-
end
-
-
1
def does_not_match?(actual)
-
wrap(actual).assert_no_title(*@args)
-
rescue Capybara::ExpectationNotMet => e
-
@failure_message_when_negated = e.message
-
return false
-
end
-
-
1
def description
-
"have title #{title.inspect}"
-
end
-
-
# RSpec 2 compatibility:
-
1
alias_method :failure_message_for_should, :failure_message
-
1
alias_method :failure_message_for_should_not, :failure_message_when_negated
-
end
-
-
1
class HaveCurrentPath < Matcher
-
1
attr_reader :current_path
-
-
1
attr_reader :failure_message, :failure_message_when_negated
-
-
1
def initialize(*args)
-
@args = args
-
-
# are set just for backwards compatability
-
@current_path = args.first
-
end
-
-
1
def matches?(actual)
-
wrap(actual).assert_current_path(*@args)
-
rescue Capybara::ExpectationNotMet => e
-
@failure_message = e.message
-
return false
-
end
-
-
1
def does_not_match?(actual)
-
wrap(actual).assert_no_current_path(*@args)
-
rescue Capybara::ExpectationNotMet => e
-
@failure_message_when_negated = e.message
-
return false
-
end
-
-
1
def description
-
"have current path #{current_path.inspect}"
-
end
-
-
# RSpec 2 compatibility:
-
1
alias_method :failure_message_for_should, :failure_message
-
1
alias_method :failure_message_for_should_not, :failure_message_when_negated
-
end
-
-
1
class BecomeClosed
-
1
def initialize(options)
-
@wait_time = Capybara::Query.new(options).wait
-
end
-
-
1
def matches?(window)
-
@window = window
-
start_time = Capybara::Helpers.monotonic_time
-
while window.exists?
-
return false if (Capybara::Helpers.monotonic_time - start_time) > @wait_time
-
sleep 0.05
-
end
-
true
-
end
-
-
1
def failure_message
-
"expected #{@window.inspect} to become closed after #{@wait_time} seconds"
-
end
-
-
1
def failure_message_when_negated
-
"expected #{@window.inspect} not to become closed after #{@wait_time} seconds"
-
end
-
-
# RSpec 2 compatibility:
-
1
alias_method :failure_message_for_should, :failure_message
-
1
alias_method :failure_message_for_should_not, :failure_message_when_negated
-
end
-
-
1
def have_selector(*args)
-
HaveSelector.new(*args)
-
end
-
-
1
def have_xpath(xpath, options={})
-
HaveSelector.new(:xpath, xpath, options)
-
end
-
-
1
def have_css(css, options={})
-
HaveSelector.new(:css, css, options)
-
end
-
-
1
def have_text(*args)
-
HaveText.new(*args)
-
end
-
1
alias_method :have_content, :have_text
-
-
1
def have_title(title, options = {})
-
HaveTitle.new(title, options)
-
end
-
-
1
def have_current_path(path, options = {})
-
HaveCurrentPath.new(path, options)
-
end
-
-
1
def have_link(locator, options={})
-
HaveSelector.new(:link, locator, options)
-
end
-
-
1
def have_button(locator, options={})
-
HaveSelector.new(:button, locator, options)
-
end
-
-
1
def have_field(locator, options={})
-
HaveSelector.new(:field, locator, options)
-
end
-
-
1
def have_checked_field(locator, options={})
-
HaveSelector.new(:field, locator, options.merge(:checked => true))
-
end
-
-
1
def have_unchecked_field(locator, options={})
-
HaveSelector.new(:field, locator, options.merge(:unchecked => true))
-
end
-
-
1
def have_select(locator, options={})
-
HaveSelector.new(:select, locator, options)
-
end
-
-
1
def have_table(locator, options={})
-
HaveSelector.new(:table, locator, options)
-
end
-
-
##
-
# Wait for window to become closed.
-
# @example
-
# expect(window).to become_closed(wait: 0.8)
-
# @param options [Hash] optional param
-
# @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum wait time
-
1
def become_closed(options = {})
-
BecomeClosed.new(options)
-
end
-
end
-
end
-
1
require 'database_cleaner/generic/base'
-
1
require 'active_record'
-
1
require 'erb'
-
-
1
module DatabaseCleaner
-
1
module ActiveRecord
-
-
1
def self.available_strategies
-
%w[truncation transaction deletion]
-
end
-
-
1
def self.config_file_location=(path)
-
@config_file_location = path
-
end
-
-
1
def self.config_file_location
-
@config_file_location ||= "#{DatabaseCleaner.app_root}/config/database.yml"
-
end
-
-
1
module Base
-
1
include ::DatabaseCleaner::Generic::Base
-
-
1
attr_accessor :connection_hash
-
-
1
def db=(desired_db)
-
3
@db = desired_db
-
3
load_config
-
end
-
-
1
def db
-
5
@db ||= super
-
end
-
-
1
def load_config
-
3
if self.db != :default && self.db.is_a?(Symbol) && File.file?(ActiveRecord.config_file_location)
-
connection_details = YAML::load(ERB.new(IO.read(ActiveRecord.config_file_location)).result)
-
@connection_hash = valid_config(connection_details)[self.db.to_s]
-
end
-
end
-
-
1
def valid_config(connection_file)
-
if !::ActiveRecord::Base.configurations.nil? && !::ActiveRecord::Base.configurations.empty?
-
if connection_file != ::ActiveRecord::Base.configurations
-
return ::ActiveRecord::Base.configurations
-
end
-
end
-
connection_file
-
end
-
-
1
def connection_class
-
@connection_class ||= if db && !db.is_a?(Symbol)
-
db
-
elsif connection_hash
-
lookup_from_connection_pool || establish_connection
-
else
-
1
::ActiveRecord::Base
-
15
end
-
end
-
-
1
private
-
-
1
def lookup_from_connection_pool
-
if ::ActiveRecord::Base.respond_to?(:descendants)
-
database_name = connection_hash["database"] || connection_hash[:database]
-
models = ::ActiveRecord::Base.descendants
-
models.detect { |m| m.connection_pool.spec.config[:database] == database_name }
-
end
-
end
-
-
1
def establish_connection
-
::ActiveRecord::Base.establish_connection(connection_hash)
-
end
-
-
end
-
end
-
end
-
1
require 'database_cleaner/active_record/base'
-
1
require 'database_cleaner/generic/transaction'
-
-
1
module DatabaseCleaner::ActiveRecord
-
1
class Transaction
-
1
include ::DatabaseCleaner::ActiveRecord::Base
-
1
include ::DatabaseCleaner::Generic::Transaction
-
-
1
def start
-
# Hack to make sure that the connection is properly setup for
-
# the clean code.
-
connection_class.connection.transaction{ }
-
-
if connection_maintains_transaction_count?
-
if connection_class.connection.respond_to?(:increment_open_transactions)
-
connection_class.connection.increment_open_transactions
-
else
-
connection_class.__send__(:increment_open_transactions)
-
end
-
end
-
if connection_class.connection.respond_to?(:begin_transaction)
-
connection_class.connection.begin_transaction :joinable => false
-
else
-
connection_class.connection.begin_db_transaction
-
end
-
end
-
-
-
1
def clean
-
connection_class.connection_pool.connections.each do |connection|
-
next unless connection.open_transactions > 0
-
-
if connection.respond_to?(:rollback_transaction)
-
connection.rollback_transaction
-
else
-
connection.rollback_db_transaction
-
end
-
-
# The below is for handling after_commit hooks.. see https://github.com/bmabey/database_cleaner/issues/99
-
if connection.respond_to?(:rollback_transaction_records, true)
-
connection.send(:rollback_transaction_records, true)
-
end
-
-
if connection_maintains_transaction_count?
-
if connection.respond_to?(:decrement_open_transactions)
-
connection.decrement_open_transactions
-
else
-
connection_class.__send__(:decrement_open_transactions)
-
end
-
end
-
end
-
end
-
-
1
def connection_maintains_transaction_count?
-
ActiveRecord::VERSION::MAJOR < 4
-
end
-
-
end
-
end
-
1
require 'active_record/base'
-
-
1
require 'active_record/connection_adapters/abstract_adapter'
-
-
#Load available connection adapters
-
%w(
-
abstract_mysql_adapter postgresql_adapter sqlite3_adapter mysql_adapter mysql2_adapter
-
1
).each do |known_adapter|
-
5
begin
-
5
require "active_record/connection_adapters/#{known_adapter}"
-
rescue LoadError
-
end
-
end
-
-
1
require "database_cleaner/generic/truncation"
-
1
require 'database_cleaner/active_record/base'
-
-
1
module DatabaseCleaner
-
1
module ConnectionAdapters
-
-
1
module AbstractAdapter
-
# used to be called views but that can clash with gems like schema_plus
-
# this gem is not meant to be exposing such an extra interface any way
-
1
def database_cleaner_view_cache
-
15
@views ||= select_values("select table_name from information_schema.views where table_schema = '#{current_database}'") rescue []
-
end
-
-
1
def database_cleaner_table_cache
-
# the adapters don't do caching (#130) but we make the assumption that the list stays the same in tests
-
15
@database_cleaner_tables ||= tables
-
end
-
-
1
def truncate_table(table_name)
-
raise NotImplementedError
-
end
-
-
1
def truncate_tables(tables)
-
tables.each do |table_name|
-
self.truncate_table(table_name)
-
end
-
end
-
end
-
-
1
module AbstractMysqlAdapter
-
1
def truncate_table(table_name)
-
execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
-
end
-
-
1
def truncate_tables(tables)
-
tables.each { |t| truncate_table(t) }
-
end
-
-
1
def pre_count_truncate_tables(tables, options = {:reset_ids => true})
-
filter = options[:reset_ids] ? method(:has_been_used?) : method(:has_rows?)
-
truncate_tables(tables.select(&filter))
-
end
-
-
1
private
-
-
1
def row_count(table)
-
# Patch for MysqlAdapter with ActiveRecord 3.2.7 later
-
# select_value("SELECT 1") #=> "1"
-
select_value("SELECT EXISTS (SELECT 1 FROM #{quote_table_name(table)} LIMIT 1)").to_i
-
end
-
-
# Returns a boolean indicating if the given table has an auto-inc number higher than 0.
-
# Note, this is different than an empty table since an table may populated, the index increased,
-
# but then the table is cleaned. In other words, this function tells us if the given table
-
# was ever inserted into.
-
1
def has_been_used?(table)
-
if has_rows?(table)
-
true
-
else
-
# Patch for MysqlAdapter with ActiveRecord 3.2.7 later
-
# select_value("SELECT 1") #=> "1"
-
select_value(<<-SQL).to_i > 1 # returns nil if not present
-
SELECT Auto_increment
-
FROM information_schema.tables
-
WHERE table_name='#{table}';
-
SQL
-
end
-
end
-
-
1
def has_rows?(table)
-
row_count(table) > 0
-
end
-
end
-
-
1
module IBM_DBAdapter
-
1
def truncate_table(table_name)
-
execute("TRUNCATE #{quote_table_name(table_name)} IMMEDIATE")
-
end
-
end
-
-
1
module SQLiteAdapter
-
1
def delete_table(table_name)
-
45
execute("DELETE FROM #{quote_table_name(table_name)};")
-
45
if uses_sequence
-
45
execute("DELETE FROM sqlite_sequence where name = '#{table_name}';")
-
end
-
end
-
1
alias truncate_table delete_table
-
-
1
def truncate_tables(tables)
-
60
tables.each { |t| truncate_table(t) }
-
end
-
-
1
private
-
-
# Returns a boolean indicating if the SQLite database is using the sqlite_sequence table.
-
1
def uses_sequence
-
45
select_value("SELECT name FROM sqlite_master WHERE type='table' AND name='sqlite_sequence';")
-
end
-
end
-
-
1
module TruncateOrDelete
-
1
def truncate_table(table_name)
-
begin
-
execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
-
rescue ::ActiveRecord::StatementInvalid
-
execute("DELETE FROM #{quote_table_name(table_name)};")
-
end
-
end
-
end
-
-
1
module OracleAdapter
-
1
def truncate_table(table_name)
-
execute("TRUNCATE TABLE #{quote_table_name(table_name)}")
-
end
-
end
-
-
1
module PostgreSQLAdapter
-
1
def db_version
-
@db_version ||= postgresql_version
-
end
-
-
1
def cascade
-
@cascade ||= db_version >= 80200 ? 'CASCADE' : ''
-
end
-
-
1
def restart_identity
-
@restart_identity ||= db_version >= 80400 ? 'RESTART IDENTITY' : ''
-
end
-
-
1
def truncate_table(table_name)
-
truncate_tables([table_name])
-
end
-
-
1
def truncate_tables(table_names)
-
return if table_names.nil? || table_names.empty?
-
execute("TRUNCATE TABLE #{table_names.map{|name| quote_table_name(name)}.join(', ')} #{restart_identity} #{cascade};")
-
end
-
-
1
def pre_count_truncate_tables(tables, options = {:reset_ids => true})
-
filter = options[:reset_ids] ? method(:has_been_used?) : method(:has_rows?)
-
truncate_tables(tables.select(&filter))
-
end
-
-
1
def database_cleaner_table_cache
-
# AR returns a list of tables without schema but then returns a
-
# migrations table with the schema. There are other problems, too,
-
# with using the base list. If a table exists in multiple schemas
-
# within the search path, truncation without the schema name could
-
# result in confusing, if not unexpected results.
-
@database_cleaner_tables ||= tables_with_schema
-
end
-
-
1
private
-
-
# Returns a boolean indicating if the given table has an auto-inc number higher than 0.
-
# Note, this is different than an empty table since an table may populated, the index increased,
-
# but then the table is cleaned. In other words, this function tells us if the given table
-
# was ever inserted into.
-
1
def has_been_used?(table)
-
return has_rows?(table) unless has_sequence?(table)
-
-
cur_val = select_value("SELECT currval('#{table}_id_seq');").to_i rescue 0
-
cur_val > 0
-
end
-
-
1
def has_sequence?(table)
-
select_value("SELECT true FROM pg_class WHERE relname = '#{table}_id_seq';")
-
end
-
-
1
def has_rows?(table)
-
select_value("SELECT true FROM #{table} LIMIT 1;")
-
end
-
-
1
def tables_with_schema
-
rows = select_rows <<-_SQL
-
SELECT schemaname || '.' || tablename
-
FROM pg_tables
-
WHERE
-
tablename !~ '_prt_' AND
-
tablename <> '#{::ActiveRecord::Migrator.schema_migrations_table_name}' AND
-
schemaname = ANY (current_schemas(false))
-
_SQL
-
rows.collect { |result| result.first }
-
end
-
end
-
end
-
end
-
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
#Apply adapter decoraters where applicable (adapter should be loaded)
-
2
AbstractAdapter.class_eval { include DatabaseCleaner::ConnectionAdapters::AbstractAdapter }
-
-
1
if defined?(JdbcAdapter)
-
if defined?(OracleJdbcConnection)
-
JdbcAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::OracleAdapter }
-
else
-
JdbcAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::TruncateOrDelete }
-
end
-
end
-
2
AbstractMysqlAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::AbstractMysqlAdapter } if defined?(AbstractMysqlAdapter)
-
1
Mysql2Adapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::AbstractMysqlAdapter } if defined?(Mysql2Adapter)
-
1
MysqlAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::AbstractMysqlAdapter } if defined?(MysqlAdapter)
-
1
SQLiteAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::SQLiteAdapter } if defined?(SQLiteAdapter)
-
2
SQLite3Adapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::SQLiteAdapter } if defined?(SQLite3Adapter)
-
2
PostgreSQLAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::PostgreSQLAdapter } if defined?(PostgreSQLAdapter)
-
1
IBM_DBAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::IBM_DBAdapter } if defined?(IBM_DBAdapter)
-
1
SQLServerAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::TruncateOrDelete } if defined?(SQLServerAdapter)
-
1
OracleEnhancedAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::OracleAdapter } if defined?(OracleEnhancedAdapter)
-
end
-
end
-
-
1
module DatabaseCleaner::ActiveRecord
-
1
class Truncation
-
1
include ::DatabaseCleaner::ActiveRecord::Base
-
1
include ::DatabaseCleaner::Generic::Truncation
-
-
1
def clean
-
15
connection = connection_class.connection
-
15
connection.disable_referential_integrity do
-
15
if pre_count? && connection.respond_to?(:pre_count_truncate_tables)
-
connection.pre_count_truncate_tables(tables_to_truncate(connection), {:reset_ids => reset_ids?})
-
else
-
15
connection.truncate_tables(tables_to_truncate(connection))
-
end
-
end
-
end
-
-
1
private
-
-
1
def tables_to_truncate(connection)
-
15
tables_in_db = cache_tables? ? connection.database_cleaner_table_cache : connection.tables
-
15
to_reject = (@tables_to_exclude + connection.database_cleaner_view_cache)
-
15
(@only || tables_in_db).reject do |table|
-
60
if ( m = table.match(/([^.]+)$/) )
-
60
to_reject.include?(m[1])
-
else
-
false
-
end
-
end
-
end
-
-
# overwritten
-
1
def migration_storage_names
-
1
[::ActiveRecord::Migrator.schema_migrations_table_name]
-
end
-
-
1
def cache_tables?
-
15
!!@cache_tables
-
end
-
-
1
def pre_count?
-
15
@pre_count == true
-
end
-
-
1
def reset_ids?
-
@reset_ids != false
-
end
-
end
-
end
-
1
module ::DatabaseCleaner
-
1
module Generic
-
1
module Base
-
-
1
def self.included(base)
-
1
base.extend(ClassMethods)
-
end
-
-
1
def db
-
:default
-
end
-
-
1
def cleaning(&block)
-
begin
-
start
-
yield
-
ensure
-
clean
-
end
-
end
-
-
1
module ClassMethods
-
1
def available_strategies
-
%W[]
-
end
-
end
-
end
-
end
-
end
-
1
module DatabaseCleaner
-
1
module Generic
-
1
module Transaction
-
1
def initialize(opts = {})
-
2
if !opts.empty?
-
raise ArgumentError, "Options are not available for transaction strategies."
-
end
-
end
-
end
-
end
-
end
-
1
module DatabaseCleaner
-
1
module Generic
-
1
module Truncation
-
1
def initialize(opts={})
-
1
if !opts.empty? && !(opts.keys - [:only, :except, :pre_count, :reset_ids, :cache_tables]).empty?
-
raise ArgumentError, "The only valid options are :only, :except, :pre_count, :reset_ids or :cache_tables. You specified #{opts.keys.join(',')}."
-
end
-
1
if opts.has_key?(:only) && opts.has_key?(:except)
-
raise ArgumentError, "You may only specify either :only or :except. Doing both doesn't really make sense does it?"
-
end
-
-
1
@only = opts[:only]
-
1
@tables_to_exclude = Array( (opts[:except] || []).dup ).flatten
-
1
@tables_to_exclude += migration_storage_names
-
1
@pre_count = opts[:pre_count]
-
1
@reset_ids = opts[:reset_ids]
-
1
@cache_tables = opts.has_key?(:cache_tables) ? !!opts[:cache_tables] : true
-
end
-
-
1
def start
-
#included for compatability reasons, do nothing if you don't need to
-
end
-
-
1
def clean
-
raise NotImplementedError
-
end
-
-
1
private
-
1
def tables_to_truncate
-
raise NotImplementedError
-
end
-
-
# overwrite in subclasses
-
# default implementation given because migration storage need not be present
-
1
def migration_storage_names
-
%w[]
-
end
-
end
-
end
-
end
-
1
require 'active_support/concern'
-
-
1
class GlobalID
-
1
module Identification
-
1
extend ActiveSupport::Concern
-
-
1
def to_global_id(options = {})
-
@global_id ||= GlobalID.create(self, options)
-
end
-
1
alias to_gid to_global_id
-
-
1
def to_gid_param(options = {})
-
to_global_id(options).to_param
-
end
-
-
1
def to_signed_global_id(options = {})
-
SignedGlobalID.create(self, options)
-
end
-
1
alias to_sgid to_signed_global_id
-
-
1
def to_sgid_param(options = {})
-
to_signed_global_id(options).to_param
-
end
-
end
-
end
-
1
module Rack
-
# Rack::Builder implements a small DSL to iteratively construct Rack
-
# applications.
-
#
-
# Example:
-
#
-
# require 'rack/lobster'
-
# app = Rack::Builder.new do
-
# use Rack::CommonLogger
-
# use Rack::ShowExceptions
-
# map "/lobster" do
-
# use Rack::Lint
-
# run Rack::Lobster.new
-
# end
-
# end
-
#
-
# run app
-
#
-
# Or
-
#
-
# app = Rack::Builder.app do
-
# use Rack::CommonLogger
-
# run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] }
-
# end
-
#
-
# run app
-
#
-
# +use+ adds middleware to the stack, +run+ dispatches to an application.
-
# You can use +map+ to construct a Rack::URLMap in a convenient way.
-
-
1
class Builder
-
1
def self.parse_file(config, opts = Server::Options.new)
-
options = {}
-
if config =~ /\.ru$/
-
cfgfile = ::File.read(config)
-
if cfgfile[/^#\\(.*)/] && opts
-
options = opts.parse! $1.split(/\s+/)
-
end
-
cfgfile.sub!(/^__END__\n.*\Z/m, '')
-
app = new_from_string cfgfile, config
-
else
-
require config
-
app = Object.const_get(::File.basename(config, '.rb').capitalize)
-
end
-
return app, options
-
end
-
-
1
def self.new_from_string(builder_script, file="(rackup)")
-
eval "Rack::Builder.new {\n" + builder_script + "\n}.to_app",
-
TOPLEVEL_BINDING, file, 0
-
end
-
-
1
def initialize(default_app = nil,&block)
-
2
@use, @map, @run, @warmup = [], nil, default_app, nil
-
2
instance_eval(&block) if block_given?
-
end
-
-
1
def self.app(default_app = nil, &block)
-
self.new(default_app, &block).to_app
-
end
-
-
# Specifies middleware to use in a stack.
-
#
-
# class Middleware
-
# def initialize(app)
-
# @app = app
-
# end
-
#
-
# def call(env)
-
# env["rack.some_header"] = "setting an example"
-
# @app.call(env)
-
# end
-
# end
-
#
-
# use Middleware
-
# run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] }
-
#
-
# All requests through to this application will first be processed by the middleware class.
-
# The +call+ method in this example sets an additional environment key which then can be
-
# referenced in the application if required.
-
1
def use(middleware, *args, &block)
-
if @map
-
mapping, @map = @map, nil
-
@use << proc { |app| generate_map app, mapping }
-
end
-
@use << proc { |app| middleware.new(app, *args, &block) }
-
end
-
-
# Takes an argument that is an object that responds to #call and returns a Rack response.
-
# The simplest form of this is a lambda object:
-
#
-
# run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] }
-
#
-
# However this could also be a class:
-
#
-
# class Heartbeat
-
# def self.call(env)
-
# [200, { "Content-Type" => "text/plain" }, ["OK"]]
-
# end
-
# end
-
#
-
# run Heartbeat
-
1
def run(app)
-
1
@run = app
-
end
-
-
# Takes a lambda or block that is used to warm-up the application.
-
#
-
# warmup do |app|
-
# client = Rack::MockRequest.new(app)
-
# client.get('/')
-
# end
-
#
-
# use SomeMiddleware
-
# run MyApp
-
1
def warmup(prc=nil, &block)
-
@warmup = prc || block
-
end
-
-
# Creates a route within the application.
-
#
-
# Rack::Builder.app do
-
# map '/' do
-
# run Heartbeat
-
# end
-
# end
-
#
-
# The +use+ method can also be used here to specify middleware to run under a specific path:
-
#
-
# Rack::Builder.app do
-
# map '/' do
-
# use Middleware
-
# run Heartbeat
-
# end
-
# end
-
#
-
# This example includes a piece of middleware which will run before requests hit +Heartbeat+.
-
#
-
1
def map(path, &block)
-
1
@map ||= {}
-
1
@map[path] = block
-
end
-
-
1
def to_app
-
2
app = @map ? generate_map(@run, @map) : @run
-
2
fail "missing run or map statement" unless app
-
2
app = @use.reverse.inject(app) { |a,e| e[a] }
-
2
@warmup.call(app) if @warmup
-
2
app
-
end
-
-
1
def call(env)
-
to_app.call(env)
-
end
-
-
1
private
-
-
1
def generate_map(default_app, mapping)
-
1
mapped = default_app ? {'/' => default_app} : {}
-
2
mapping.each { |r,b| mapped[r] = self.class.new(default_app, &b).to_app }
-
1
URLMap.new(mapped)
-
end
-
end
-
end
-
1
require 'rack/utils'
-
-
1
module Rack
-
-
# Middleware that applies chunked transfer encoding to response bodies
-
# when the response does not include a Content-Length header.
-
1
class Chunked
-
1
include Rack::Utils
-
-
# A body wrapper that emits chunked responses
-
1
class Body
-
1
TERM = "\r\n"
-
1
TAIL = "0#{TERM}#{TERM}"
-
-
1
include Rack::Utils
-
-
1
def initialize(body)
-
@body = body
-
end
-
-
1
def each
-
term = TERM
-
@body.each do |chunk|
-
size = bytesize(chunk)
-
next if size == 0
-
-
chunk = chunk.dup.force_encoding(Encoding::BINARY) if chunk.respond_to?(:force_encoding)
-
yield [size.to_s(16), term, chunk, term].join
-
end
-
yield TAIL
-
end
-
-
1
def close
-
@body.close if @body.respond_to?(:close)
-
end
-
end
-
-
1
def initialize(app)
-
@app = app
-
end
-
-
# pre-HTTP/1.0 (informally "HTTP/0.9") HTTP requests did not have
-
# a version (nor response headers)
-
1
def chunkable_version?(ver)
-
case ver
-
when "HTTP/1.0", nil, "HTTP/0.9"
-
false
-
else
-
true
-
end
-
end
-
-
1
def call(env)
-
status, headers, body = @app.call(env)
-
headers = HeaderHash.new(headers)
-
-
if ! chunkable_version?(env['HTTP_VERSION']) ||
-
STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
-
headers[CONTENT_LENGTH] ||
-
headers['Transfer-Encoding']
-
[status, headers, body]
-
else
-
headers.delete(CONTENT_LENGTH)
-
headers['Transfer-Encoding'] = 'chunked'
-
[status, headers, Body.new(body)]
-
end
-
end
-
end
-
end
-
1
require 'rack/utils'
-
1
require 'forwardable'
-
-
1
module Rack
-
# Rack::Lint validates your application and the requests and
-
# responses according to the Rack spec.
-
-
1
class Lint
-
1
def initialize(app)
-
@app = app
-
@content_length = nil
-
end
-
-
# :stopdoc:
-
-
1
class LintError < RuntimeError; end
-
1
module Assertion
-
1
def assert(message, &block)
-
unless block.call
-
raise LintError, message
-
end
-
end
-
end
-
1
include Assertion
-
-
## This specification aims to formalize the Rack protocol. You
-
## can (and should) use Rack::Lint to enforce it.
-
##
-
## When you develop middleware, be sure to add a Lint before and
-
## after to catch all mistakes.
-
-
## = Rack applications
-
-
## A Rack application is a Ruby object (not a class) that
-
## responds to +call+.
-
1
def call(env=nil)
-
dup._call(env)
-
end
-
-
1
def _call(env)
-
## It takes exactly one argument, the *environment*
-
assert("No env given") { env }
-
check_env env
-
-
env['rack.input'] = InputWrapper.new(env['rack.input'])
-
env['rack.errors'] = ErrorWrapper.new(env['rack.errors'])
-
-
## and returns an Array of exactly three values:
-
status, headers, @body = @app.call(env)
-
## The *status*,
-
check_status status
-
## the *headers*,
-
check_headers headers
-
-
check_hijack_response headers, env
-
-
## and the *body*.
-
check_content_type status, headers
-
check_content_length status, headers
-
@head_request = env[REQUEST_METHOD] == "HEAD"
-
[status, headers, self]
-
end
-
-
## == The Environment
-
1
def check_env(env)
-
## The environment must be an instance of Hash that includes
-
## CGI-like headers. The application is free to modify the
-
## environment.
-
assert("env #{env.inspect} is not a Hash, but #{env.class}") {
-
env.kind_of? Hash
-
}
-
-
##
-
## The environment is required to include these variables
-
## (adopted from PEP333), except when they'd be empty, but see
-
## below.
-
-
## <tt>REQUEST_METHOD</tt>:: The HTTP request method, such as
-
## "GET" or "POST". This cannot ever
-
## be an empty string, and so is
-
## always required.
-
-
## <tt>SCRIPT_NAME</tt>:: The initial portion of the request
-
## URL's "path" that corresponds to the
-
## application object, so that the
-
## application knows its virtual
-
## "location". This may be an empty
-
## string, if the application corresponds
-
## to the "root" of the server.
-
-
## <tt>PATH_INFO</tt>:: The remainder of the request URL's
-
## "path", designating the virtual
-
## "location" of the request's target
-
## within the application. This may be an
-
## empty string, if the request URL targets
-
## the application root and does not have a
-
## trailing slash. This value may be
-
## percent-encoded when I originating from
-
## a URL.
-
-
## <tt>QUERY_STRING</tt>:: The portion of the request URL that
-
## follows the <tt>?</tt>, if any. May be
-
## empty, but is always required!
-
-
## <tt>SERVER_NAME</tt>, <tt>SERVER_PORT</tt>::
-
## When combined with <tt>SCRIPT_NAME</tt> and
-
## <tt>PATH_INFO</tt>, these variables can be
-
## used to complete the URL. Note, however,
-
## that <tt>HTTP_HOST</tt>, if present,
-
## should be used in preference to
-
## <tt>SERVER_NAME</tt> for reconstructing
-
## the request URL.
-
## <tt>SERVER_NAME</tt> and <tt>SERVER_PORT</tt>
-
## can never be empty strings, and so
-
## are always required.
-
-
## <tt>HTTP_</tt> Variables:: Variables corresponding to the
-
## client-supplied HTTP request
-
## headers (i.e., variables whose
-
## names begin with <tt>HTTP_</tt>). The
-
## presence or absence of these
-
## variables should correspond with
-
## the presence or absence of the
-
## appropriate HTTP header in the
-
## request. See
-
## <a href="https://tools.ietf.org/html/rfc3875#section-4.1.18">
-
## RFC3875 section 4.1.18</a> for
-
## specific behavior.
-
-
## In addition to this, the Rack environment must include these
-
## Rack-specific variables:
-
-
## <tt>rack.version</tt>:: The Array representing this version of Rack
-
## See Rack::VERSION, that corresponds to
-
## the version of this SPEC.
-
-
## <tt>rack.url_scheme</tt>:: +http+ or +https+, depending on the
-
## request URL.
-
-
## <tt>rack.input</tt>:: See below, the input stream.
-
-
## <tt>rack.errors</tt>:: See below, the error stream.
-
-
## <tt>rack.multithread</tt>:: true if the application object may be
-
## simultaneously invoked by another thread
-
## in the same process, false otherwise.
-
-
## <tt>rack.multiprocess</tt>:: true if an equivalent application object
-
## may be simultaneously invoked by another
-
## process, false otherwise.
-
-
## <tt>rack.run_once</tt>:: true if the server expects
-
## (but does not guarantee!) that the
-
## application will only be invoked this one
-
## time during the life of its containing
-
## process. Normally, this will only be true
-
## for a server based on CGI
-
## (or something similar).
-
-
## <tt>rack.hijack?</tt>:: present and true if the server supports
-
## connection hijacking. See below, hijacking.
-
-
## <tt>rack.hijack</tt>:: an object responding to #call that must be
-
## called at least once before using
-
## rack.hijack_io.
-
## It is recommended #call return rack.hijack_io
-
## as well as setting it in env if necessary.
-
-
## <tt>rack.hijack_io</tt>:: if rack.hijack? is true, and rack.hijack
-
## has received #call, this will contain
-
## an object resembling an IO. See hijacking.
-
-
## Additional environment specifications have approved to
-
## standardized middleware APIs. None of these are required to
-
## be implemented by the server.
-
-
## <tt>rack.session</tt>:: A hash like interface for storing
-
## request session data.
-
## The store must implement:
-
if session = env['rack.session']
-
## store(key, value) (aliased as []=);
-
assert("session #{session.inspect} must respond to store and []=") {
-
session.respond_to?(:store) && session.respond_to?(:[]=)
-
}
-
-
## fetch(key, default = nil) (aliased as []);
-
assert("session #{session.inspect} must respond to fetch and []") {
-
session.respond_to?(:fetch) && session.respond_to?(:[])
-
}
-
-
## delete(key);
-
assert("session #{session.inspect} must respond to delete") {
-
session.respond_to?(:delete)
-
}
-
-
## clear;
-
assert("session #{session.inspect} must respond to clear") {
-
session.respond_to?(:clear)
-
}
-
end
-
-
## <tt>rack.logger</tt>:: A common object interface for logging messages.
-
## The object must implement:
-
if logger = env['rack.logger']
-
## info(message, &block)
-
assert("logger #{logger.inspect} must respond to info") {
-
logger.respond_to?(:info)
-
}
-
-
## debug(message, &block)
-
assert("logger #{logger.inspect} must respond to debug") {
-
logger.respond_to?(:debug)
-
}
-
-
## warn(message, &block)
-
assert("logger #{logger.inspect} must respond to warn") {
-
logger.respond_to?(:warn)
-
}
-
-
## error(message, &block)
-
assert("logger #{logger.inspect} must respond to error") {
-
logger.respond_to?(:error)
-
}
-
-
## fatal(message, &block)
-
assert("logger #{logger.inspect} must respond to fatal") {
-
logger.respond_to?(:fatal)
-
}
-
end
-
-
## <tt>rack.multipart.buffer_size</tt>:: An Integer hint to the multipart parser as to what chunk size to use for reads and writes.
-
if bufsize = env['rack.multipart.buffer_size']
-
assert("rack.multipart.buffer_size must be an Integer > 0 if specified") {
-
bufsize.is_a?(Integer) && bufsize > 0
-
}
-
end
-
-
## <tt>rack.multipart.tempfile_factory</tt>:: An object responding to #call with two arguments, the filename and content_type given for the multipart form field, and returning an IO-like object that responds to #<< and optionally #rewind. This factory will be used to instantiate the tempfile for each multipart form file upload field, rather than the default class of Tempfile.
-
if tempfile_factory = env['rack.multipart.tempfile_factory']
-
assert("rack.multipart.tempfile_factory must respond to #call") { tempfile_factory.respond_to?(:call) }
-
env['rack.multipart.tempfile_factory'] = lambda do |filename, content_type|
-
io = tempfile_factory.call(filename, content_type)
-
assert("rack.multipart.tempfile_factory return value must respond to #<<") { io.respond_to?(:<<) }
-
io
-
end
-
end
-
-
## The server or the application can store their own data in the
-
## environment, too. The keys must contain at least one dot,
-
## and should be prefixed uniquely. The prefix <tt>rack.</tt>
-
## is reserved for use with the Rack core distribution and other
-
## accepted specifications and must not be used otherwise.
-
##
-
-
%w[REQUEST_METHOD SERVER_NAME SERVER_PORT
-
QUERY_STRING
-
rack.version rack.input rack.errors
-
rack.multithread rack.multiprocess rack.run_once].each { |header|
-
assert("env missing required key #{header}") { env.include? header }
-
}
-
-
## The environment must not contain the keys
-
## <tt>HTTP_CONTENT_TYPE</tt> or <tt>HTTP_CONTENT_LENGTH</tt>
-
## (use the versions without <tt>HTTP_</tt>).
-
%w[HTTP_CONTENT_TYPE HTTP_CONTENT_LENGTH].each { |header|
-
assert("env contains #{header}, must use #{header[5,-1]}") {
-
not env.include? header
-
}
-
}
-
-
## The CGI keys (named without a period) must have String values.
-
env.each { |key, value|
-
next if key.include? "." # Skip extensions
-
assert("env variable #{key} has non-string value #{value.inspect}") {
-
value.kind_of? String
-
}
-
}
-
-
## There are the following restrictions:
-
-
## * <tt>rack.version</tt> must be an array of Integers.
-
assert("rack.version must be an Array, was #{env["rack.version"].class}") {
-
env["rack.version"].kind_of? Array
-
}
-
## * <tt>rack.url_scheme</tt> must either be +http+ or +https+.
-
assert("rack.url_scheme unknown: #{env["rack.url_scheme"].inspect}") {
-
%w[http https].include? env["rack.url_scheme"]
-
}
-
-
## * There must be a valid input stream in <tt>rack.input</tt>.
-
check_input env["rack.input"]
-
## * There must be a valid error stream in <tt>rack.errors</tt>.
-
check_error env["rack.errors"]
-
## * There may be a valid hijack stream in <tt>rack.hijack_io</tt>
-
check_hijack env
-
-
## * The <tt>REQUEST_METHOD</tt> must be a valid token.
-
assert("REQUEST_METHOD unknown: #{env[REQUEST_METHOD]}") {
-
env["REQUEST_METHOD"] =~ /\A[0-9A-Za-z!\#$%&'*+.^_`|~-]+\z/
-
}
-
-
## * The <tt>SCRIPT_NAME</tt>, if non-empty, must start with <tt>/</tt>
-
assert("SCRIPT_NAME must start with /") {
-
!env.include?("SCRIPT_NAME") ||
-
env["SCRIPT_NAME"] == "" ||
-
env["SCRIPT_NAME"] =~ /\A\//
-
}
-
## * The <tt>PATH_INFO</tt>, if non-empty, must start with <tt>/</tt>
-
assert("PATH_INFO must start with /") {
-
!env.include?("PATH_INFO") ||
-
env["PATH_INFO"] == "" ||
-
env["PATH_INFO"] =~ /\A\//
-
}
-
## * The <tt>CONTENT_LENGTH</tt>, if given, must consist of digits only.
-
assert("Invalid CONTENT_LENGTH: #{env["CONTENT_LENGTH"]}") {
-
!env.include?("CONTENT_LENGTH") || env["CONTENT_LENGTH"] =~ /\A\d+\z/
-
}
-
-
## * One of <tt>SCRIPT_NAME</tt> or <tt>PATH_INFO</tt> must be
-
## set. <tt>PATH_INFO</tt> should be <tt>/</tt> if
-
## <tt>SCRIPT_NAME</tt> is empty.
-
assert("One of SCRIPT_NAME or PATH_INFO must be set (make PATH_INFO '/' if SCRIPT_NAME is empty)") {
-
env["SCRIPT_NAME"] || env["PATH_INFO"]
-
}
-
## <tt>SCRIPT_NAME</tt> never should be <tt>/</tt>, but instead be empty.
-
assert("SCRIPT_NAME cannot be '/', make it '' and PATH_INFO '/'") {
-
env["SCRIPT_NAME"] != "/"
-
}
-
end
-
-
## === The Input Stream
-
##
-
## The input stream is an IO-like object which contains the raw HTTP
-
## POST data.
-
1
def check_input(input)
-
## When applicable, its external encoding must be "ASCII-8BIT" and it
-
## must be opened in binary mode, for Ruby 1.9 compatibility.
-
assert("rack.input #{input} does not have ASCII-8BIT as its external encoding") {
-
input.external_encoding.name == "ASCII-8BIT"
-
} if input.respond_to?(:external_encoding)
-
assert("rack.input #{input} is not opened in binary mode") {
-
input.binmode?
-
} if input.respond_to?(:binmode?)
-
-
## The input stream must respond to +gets+, +each+, +read+ and +rewind+.
-
[:gets, :each, :read, :rewind].each { |method|
-
assert("rack.input #{input} does not respond to ##{method}") {
-
input.respond_to? method
-
}
-
}
-
end
-
-
1
class InputWrapper
-
1
include Assertion
-
-
1
def initialize(input)
-
@input = input
-
end
-
-
## * +gets+ must be called without arguments and return a string,
-
## or +nil+ on EOF.
-
1
def gets(*args)
-
assert("rack.input#gets called with arguments") { args.size == 0 }
-
v = @input.gets
-
assert("rack.input#gets didn't return a String") {
-
v.nil? or v.kind_of? String
-
}
-
v
-
end
-
-
## * +read+ behaves like IO#read.
-
## Its signature is <tt>read([length, [buffer]])</tt>.
-
##
-
## If given, +length+ must be a non-negative Integer (>= 0) or +nil+,
-
## and +buffer+ must be a String and may not be nil.
-
##
-
## If +length+ is given and not nil, then this method reads at most
-
## +length+ bytes from the input stream.
-
##
-
## If +length+ is not given or nil, then this method reads
-
## all data until EOF.
-
##
-
## When EOF is reached, this method returns nil if +length+ is given
-
## and not nil, or "" if +length+ is not given or is nil.
-
##
-
## If +buffer+ is given, then the read data will be placed
-
## into +buffer+ instead of a newly created String object.
-
1
def read(*args)
-
assert("rack.input#read called with too many arguments") {
-
args.size <= 2
-
}
-
if args.size >= 1
-
assert("rack.input#read called with non-integer and non-nil length") {
-
args.first.kind_of?(Integer) || args.first.nil?
-
}
-
assert("rack.input#read called with a negative length") {
-
args.first.nil? || args.first >= 0
-
}
-
end
-
if args.size >= 2
-
assert("rack.input#read called with non-String buffer") {
-
args[1].kind_of?(String)
-
}
-
end
-
-
v = @input.read(*args)
-
-
assert("rack.input#read didn't return nil or a String") {
-
v.nil? or v.kind_of? String
-
}
-
if args[0].nil?
-
assert("rack.input#read(nil) returned nil on EOF") {
-
!v.nil?
-
}
-
end
-
-
v
-
end
-
-
## * +each+ must be called without arguments and only yield Strings.
-
1
def each(*args)
-
assert("rack.input#each called with arguments") { args.size == 0 }
-
@input.each { |line|
-
assert("rack.input#each didn't yield a String") {
-
line.kind_of? String
-
}
-
yield line
-
}
-
end
-
-
## * +rewind+ must be called without arguments. It rewinds the input
-
## stream back to the beginning. It must not raise Errno::ESPIPE:
-
## that is, it may not be a pipe or a socket. Therefore, handler
-
## developers must buffer the input data into some rewindable object
-
## if the underlying input stream is not rewindable.
-
1
def rewind(*args)
-
assert("rack.input#rewind called with arguments") { args.size == 0 }
-
assert("rack.input#rewind raised Errno::ESPIPE") {
-
begin
-
@input.rewind
-
true
-
rescue Errno::ESPIPE
-
false
-
end
-
}
-
end
-
-
## * +close+ must never be called on the input stream.
-
1
def close(*args)
-
assert("rack.input#close must not be called") { false }
-
end
-
end
-
-
## === The Error Stream
-
1
def check_error(error)
-
## The error stream must respond to +puts+, +write+ and +flush+.
-
[:puts, :write, :flush].each { |method|
-
assert("rack.error #{error} does not respond to ##{method}") {
-
error.respond_to? method
-
}
-
}
-
end
-
-
1
class ErrorWrapper
-
1
include Assertion
-
-
1
def initialize(error)
-
@error = error
-
end
-
-
## * +puts+ must be called with a single argument that responds to +to_s+.
-
1
def puts(str)
-
@error.puts str
-
end
-
-
## * +write+ must be called with a single argument that is a String.
-
1
def write(str)
-
assert("rack.errors#write not called with a String") { str.kind_of? String }
-
@error.write str
-
end
-
-
## * +flush+ must be called without arguments and must be called
-
## in order to make the error appear for sure.
-
1
def flush
-
@error.flush
-
end
-
-
## * +close+ must never be called on the error stream.
-
1
def close(*args)
-
assert("rack.errors#close must not be called") { false }
-
end
-
end
-
-
1
class HijackWrapper
-
1
include Assertion
-
1
extend Forwardable
-
-
1
REQUIRED_METHODS = [
-
:read, :write, :read_nonblock, :write_nonblock, :flush, :close,
-
:close_read, :close_write, :closed?
-
]
-
-
1
def_delegators :@io, *REQUIRED_METHODS
-
-
1
def initialize(io)
-
@io = io
-
REQUIRED_METHODS.each do |meth|
-
assert("rack.hijack_io must respond to #{meth}") { io.respond_to? meth }
-
end
-
end
-
end
-
-
## === Hijacking
-
#
-
# AUTHORS: n.b. The trailing whitespace between paragraphs is important and
-
# should not be removed. The whitespace creates paragraphs in the RDoc
-
# output.
-
#
-
## ==== Request (before status)
-
1
def check_hijack(env)
-
if env['rack.hijack?']
-
## If rack.hijack? is true then rack.hijack must respond to #call.
-
original_hijack = env['rack.hijack']
-
assert("rack.hijack must respond to call") { original_hijack.respond_to?(:call) }
-
env['rack.hijack'] = proc do
-
## rack.hijack must return the io that will also be assigned (or is
-
## already present, in rack.hijack_io.
-
io = original_hijack.call
-
HijackWrapper.new(io)
-
##
-
## rack.hijack_io must respond to:
-
## <tt>read, write, read_nonblock, write_nonblock, flush, close,
-
## close_read, close_write, closed?</tt>
-
##
-
## The semantics of these IO methods must be a best effort match to
-
## those of a normal ruby IO or Socket object, using standard
-
## arguments and raising standard exceptions. Servers are encouraged
-
## to simply pass on real IO objects, although it is recognized that
-
## this approach is not directly compatible with SPDY and HTTP 2.0.
-
##
-
## IO provided in rack.hijack_io should preference the
-
## IO::WaitReadable and IO::WaitWritable APIs wherever supported.
-
##
-
## There is a deliberate lack of full specification around
-
## rack.hijack_io, as semantics will change from server to server.
-
## Users are encouraged to utilize this API with a knowledge of their
-
## server choice, and servers may extend the functionality of
-
## hijack_io to provide additional features to users. The purpose of
-
## rack.hijack is for Rack to "get out of the way", as such, Rack only
-
## provides the minimum of specification and support.
-
env['rack.hijack_io'] = HijackWrapper.new(env['rack.hijack_io'])
-
io
-
end
-
else
-
##
-
## If rack.hijack? is false, then rack.hijack should not be set.
-
assert("rack.hijack? is false, but rack.hijack is present") { env['rack.hijack'].nil? }
-
##
-
## If rack.hijack? is false, then rack.hijack_io should not be set.
-
assert("rack.hijack? is false, but rack.hijack_io is present") { env['rack.hijack_io'].nil? }
-
end
-
end
-
-
## ==== Response (after headers)
-
## It is also possible to hijack a response after the status and headers
-
## have been sent.
-
1
def check_hijack_response(headers, env)
-
-
# this check uses headers like a hash, but the spec only requires
-
# headers respond to #each
-
headers = Rack::Utils::HeaderHash.new(headers)
-
-
## In order to do this, an application may set the special header
-
## <tt>rack.hijack</tt> to an object that responds to <tt>call</tt>
-
## accepting an argument that conforms to the <tt>rack.hijack_io</tt>
-
## protocol.
-
##
-
## After the headers have been sent, and this hijack callback has been
-
## called, the application is now responsible for the remaining lifecycle
-
## of the IO. The application is also responsible for maintaining HTTP
-
## semantics. Of specific note, in almost all cases in the current SPEC,
-
## applications will have wanted to specify the header Connection:close in
-
## HTTP/1.1, and not Connection:keep-alive, as there is no protocol for
-
## returning hijacked sockets to the web server. For that purpose, use the
-
## body streaming API instead (progressively yielding strings via each).
-
##
-
## Servers must ignore the <tt>body</tt> part of the response tuple when
-
## the <tt>rack.hijack</tt> response API is in use.
-
-
if env['rack.hijack?'] && headers['rack.hijack']
-
assert('rack.hijack header must respond to #call') {
-
headers['rack.hijack'].respond_to? :call
-
}
-
original_hijack = headers['rack.hijack']
-
headers['rack.hijack'] = proc do |io|
-
original_hijack.call HijackWrapper.new(io)
-
end
-
else
-
##
-
## The special response header <tt>rack.hijack</tt> must only be set
-
## if the request env has <tt>rack.hijack?</tt> <tt>true</tt>.
-
assert('rack.hijack header must not be present if server does not support hijacking') {
-
headers['rack.hijack'].nil?
-
}
-
end
-
end
-
## ==== Conventions
-
## * Middleware should not use hijack unless it is handling the whole
-
## response.
-
## * Middleware may wrap the IO object for the response pattern.
-
## * Middleware should not wrap the IO object for the request pattern. The
-
## request pattern is intended to provide the hijacker with "raw tcp".
-
-
## == The Response
-
-
## === The Status
-
1
def check_status(status)
-
## This is an HTTP status. When parsed as integer (+to_i+), it must be
-
## greater than or equal to 100.
-
assert("Status must be >=100 seen as integer") { status.to_i >= 100 }
-
end
-
-
## === The Headers
-
1
def check_headers(header)
-
## The header must respond to +each+, and yield values of key and value.
-
assert("headers object should respond to #each, but doesn't (got #{header.class} as headers)") {
-
header.respond_to? :each
-
}
-
header.each { |key, value|
-
## Special headers starting "rack." are for communicating with the
-
## server, and must not be sent back to the client.
-
next if key =~ /^rack\..+$/
-
-
## The header keys must be Strings.
-
assert("header key must be a string, was #{key.class}") {
-
key.kind_of? String
-
}
-
## The header must not contain a +Status+ key.
-
assert("header must not contain Status") { key.downcase != "status" }
-
## The header must conform to RFC7230 token specification, i.e. cannot
-
## contain non-printable ASCII, DQUOTE or "(),/:;<=>?@[\]{}".
-
assert("invalid header name: #{key}") { key !~ /[\(\),\/:;<=>\?@\[\\\]{}[:cntrl:]]/ }
-
-
## The values of the header must be Strings,
-
assert("a header value must be a String, but the value of " +
-
"'#{key}' is a #{value.class}") { value.kind_of? String }
-
## consisting of lines (for multiple header values, e.g. multiple
-
## <tt>Set-Cookie</tt> values) separated by "\\n".
-
value.split("\n").each { |item|
-
## The lines must not contain characters below 037.
-
assert("invalid header value #{key}: #{item.inspect}") {
-
item !~ /[\000-\037]/
-
}
-
}
-
}
-
end
-
-
## === The Content-Type
-
1
def check_content_type(status, headers)
-
headers.each { |key, value|
-
## There must not be a <tt>Content-Type</tt>, when the +Status+ is 1xx,
-
## 204, 205 or 304.
-
if key.downcase == "content-type"
-
assert("Content-Type header found in #{status} response, not allowed") {
-
not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
-
}
-
return
-
end
-
}
-
end
-
-
## === The Content-Length
-
1
def check_content_length(status, headers)
-
headers.each { |key, value|
-
if key.downcase == 'content-length'
-
## There must not be a <tt>Content-Length</tt> header when the
-
## +Status+ is 1xx, 204, 205 or 304.
-
assert("Content-Length header found in #{status} response, not allowed") {
-
not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
-
}
-
@content_length = value
-
end
-
}
-
end
-
-
1
def verify_content_length(bytes)
-
if @head_request
-
assert("Response body was given for HEAD request, but should be empty") {
-
bytes == 0
-
}
-
elsif @content_length
-
assert("Content-Length header was #{@content_length}, but should be #{bytes}") {
-
@content_length == bytes.to_s
-
}
-
end
-
end
-
-
## === The Body
-
1
def each
-
@closed = false
-
bytes = 0
-
-
## The Body must respond to +each+
-
assert("Response body must respond to each") do
-
@body.respond_to?(:each)
-
end
-
-
@body.each { |part|
-
## and must only yield String values.
-
assert("Body yielded non-string value #{part.inspect}") {
-
part.kind_of? String
-
}
-
bytes += Rack::Utils.bytesize(part)
-
yield part
-
}
-
verify_content_length(bytes)
-
-
##
-
## The Body itself should not be an instance of String, as this will
-
## break in Ruby 1.9.
-
##
-
## If the Body responds to +close+, it will be called after iteration. If
-
## the body is replaced by a middleware after action, the original body
-
## must be closed first, if it responds to close.
-
# XXX howto: assert("Body has not been closed") { @closed }
-
-
-
##
-
## If the Body responds to +to_path+, it must return a String
-
## identifying the location of a file whose contents are identical
-
## to that produced by calling +each+; this may be used by the
-
## server as an alternative, possibly more efficient way to
-
## transport the response.
-
-
if @body.respond_to?(:to_path)
-
assert("The file identified by body.to_path does not exist") {
-
::File.exist? @body.to_path
-
}
-
end
-
-
##
-
## The Body commonly is an Array of Strings, the application
-
## instance itself, or a File-like object.
-
end
-
-
1
def close
-
@closed = true
-
@body.close if @body.respond_to?(:close)
-
end
-
-
# :startdoc:
-
-
end
-
end
-
-
## == Thanks
-
## Some parts of this specification are adopted from PEP333: Python
-
## Web Server Gateway Interface
-
## v1.0 (http://www.python.org/dev/peps/pep-0333/). I'd like to thank
-
## everyone involved in that effort.
-
1
require 'uri'
-
1
require 'stringio'
-
1
require 'rack'
-
1
require 'rack/lint'
-
1
require 'rack/utils'
-
1
require 'rack/response'
-
-
1
module Rack
-
# Rack::MockRequest helps testing your Rack application without
-
# actually using HTTP.
-
#
-
# After performing a request on a URL with get/post/put/patch/delete, it
-
# returns a MockResponse with useful helper methods for effective
-
# testing.
-
#
-
# You can pass a hash with additional configuration to the
-
# get/post/put/patch/delete.
-
# <tt>:input</tt>:: A String or IO-like to be used as rack.input.
-
# <tt>:fatal</tt>:: Raise a FatalWarning if the app writes to rack.errors.
-
# <tt>:lint</tt>:: If true, wrap the application in a Rack::Lint.
-
-
1
class MockRequest
-
1
class FatalWarning < RuntimeError
-
end
-
-
1
class FatalWarner
-
1
def puts(warning)
-
raise FatalWarning, warning
-
end
-
-
1
def write(warning)
-
raise FatalWarning, warning
-
end
-
-
1
def flush
-
end
-
-
1
def string
-
""
-
end
-
end
-
-
1
DEFAULT_ENV = {
-
"rack.version" => Rack::VERSION,
-
"rack.input" => StringIO.new,
-
"rack.errors" => StringIO.new,
-
"rack.multithread" => true,
-
"rack.multiprocess" => true,
-
"rack.run_once" => false,
-
}
-
-
1
def initialize(app)
-
@app = app
-
end
-
-
1
def get(uri, opts={}) request("GET", uri, opts) end
-
1
def post(uri, opts={}) request("POST", uri, opts) end
-
1
def put(uri, opts={}) request("PUT", uri, opts) end
-
1
def patch(uri, opts={}) request("PATCH", uri, opts) end
-
1
def delete(uri, opts={}) request("DELETE", uri, opts) end
-
1
def head(uri, opts={}) request("HEAD", uri, opts) end
-
1
def options(uri, opts={}) request("OPTIONS", uri, opts) end
-
-
1
def request(method="GET", uri="", opts={})
-
env = self.class.env_for(uri, opts.merge(:method => method))
-
-
if opts[:lint]
-
app = Rack::Lint.new(@app)
-
else
-
app = @app
-
end
-
-
errors = env["rack.errors"]
-
status, headers, body = app.call(env)
-
MockResponse.new(status, headers, body, errors)
-
ensure
-
body.close if body.respond_to?(:close)
-
end
-
-
# For historical reasons, we're pinning to RFC 2396. It's easier for users
-
# and we get support from ruby 1.8 to 2.2 using this method.
-
1
def self.parse_uri_rfc2396(uri)
-
1
@parser ||= defined?(URI::RFC2396_Parser) ? URI::RFC2396_Parser.new : URI
-
1
@parser.parse(uri)
-
end
-
-
# Return the Rack environment used for a request to +uri+.
-
1
def self.env_for(uri="", opts={})
-
1
uri = parse_uri_rfc2396(uri)
-
1
uri.path = "/#{uri.path}" unless uri.path[0] == ?/
-
-
1
env = DEFAULT_ENV.dup
-
-
1
env[REQUEST_METHOD] = opts[:method] ? opts[:method].to_s.upcase : "GET"
-
1
env["SERVER_NAME"] = uri.host || "example.org"
-
1
env["SERVER_PORT"] = uri.port ? uri.port.to_s : "80"
-
1
env[QUERY_STRING] = uri.query.to_s
-
1
env[PATH_INFO] = (!uri.path || uri.path.empty?) ? "/" : uri.path
-
1
env["rack.url_scheme"] = uri.scheme || "http"
-
1
env["HTTPS"] = env["rack.url_scheme"] == "https" ? "on" : "off"
-
-
1
env[SCRIPT_NAME] = opts[:script_name] || ""
-
-
1
if opts[:fatal]
-
env["rack.errors"] = FatalWarner.new
-
else
-
1
env["rack.errors"] = StringIO.new
-
end
-
-
1
if params = opts[:params]
-
if env[REQUEST_METHOD] == "GET"
-
params = Utils.parse_nested_query(params) if params.is_a?(String)
-
params.update(Utils.parse_nested_query(env[QUERY_STRING]))
-
env[QUERY_STRING] = Utils.build_nested_query(params)
-
elsif !opts.has_key?(:input)
-
opts["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
-
if params.is_a?(Hash)
-
if data = Utils::Multipart.build_multipart(params)
-
opts[:input] = data
-
opts["CONTENT_LENGTH"] ||= data.length.to_s
-
opts["CONTENT_TYPE"] = "multipart/form-data; boundary=#{Utils::Multipart::MULTIPART_BOUNDARY}"
-
else
-
opts[:input] = Utils.build_nested_query(params)
-
end
-
else
-
opts[:input] = params
-
end
-
end
-
end
-
-
1
empty_str = ""
-
1
empty_str.force_encoding("ASCII-8BIT") if empty_str.respond_to? :force_encoding
-
1
opts[:input] ||= empty_str
-
1
if String === opts[:input]
-
1
rack_input = StringIO.new(opts[:input])
-
else
-
rack_input = opts[:input]
-
end
-
-
1
rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
-
1
env['rack.input'] = rack_input
-
-
1
env["CONTENT_LENGTH"] ||= env["rack.input"].length.to_s
-
-
1
opts.each { |field, value|
-
4
env[field] = value if String === field
-
}
-
-
1
env
-
end
-
end
-
-
# Rack::MockResponse provides useful helpers for testing your apps.
-
# Usually, you don't create the MockResponse on your own, but use
-
# MockRequest.
-
-
1
class MockResponse < Rack::Response
-
# Headers
-
1
attr_reader :original_headers
-
-
# Errors
-
1
attr_accessor :errors
-
-
1
def initialize(status, headers, body, errors=StringIO.new(""))
-
@original_headers = headers
-
@errors = errors.string if errors.respond_to?(:string)
-
@body_string = nil
-
-
super(body, status, headers)
-
end
-
-
1
def =~(other)
-
body =~ other
-
end
-
-
1
def match(other)
-
body.match other
-
end
-
-
1
def body
-
# FIXME: apparently users of MockResponse expect the return value of
-
# MockResponse#body to be a string. However, the real response object
-
# returns the body as a list.
-
#
-
# See spec_showstatus.rb:
-
#
-
# should "not replace existing messages" do
-
# ...
-
# res.body.should == "foo!"
-
# end
-
super.join
-
end
-
-
1
def empty?
-
[201, 204, 205, 304].include? status
-
end
-
end
-
end
-
1
module Rack
-
# Rack::URLMap takes a hash mapping urls or paths to apps, and
-
# dispatches accordingly. Support for HTTP/1.1 host names exists if
-
# the URLs start with <tt>http://</tt> or <tt>https://</tt>.
-
#
-
# URLMap modifies the SCRIPT_NAME and PATH_INFO such that the part
-
# relevant for dispatch is in the SCRIPT_NAME, and the rest in the
-
# PATH_INFO. This should be taken care of when you need to
-
# reconstruct the URL in order to create links.
-
#
-
# URLMap dispatches in such a way that the longest paths are tried
-
# first, since they are most specific.
-
-
1
class URLMap
-
1
NEGATIVE_INFINITY = -1.0 / 0.0
-
1
INFINITY = 1.0 / 0.0
-
-
1
def initialize(map = {})
-
1
remap(map)
-
end
-
-
1
def remap(map)
-
1
@mapping = map.map { |location, app|
-
1
if location =~ %r{\Ahttps?://(.*?)(/.*)}
-
host, location = $1, $2
-
else
-
1
host = nil
-
end
-
-
1
unless location[0] == ?/
-
raise ArgumentError, "paths need to start with /"
-
end
-
-
1
location = location.chomp('/')
-
1
match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n')
-
-
1
[host, location, match, app]
-
}.sort_by do |(host, location, _, _)|
-
1
[host ? -host.size : INFINITY, -location.size]
-
end
-
end
-
-
1
def call(env)
-
path = env[PATH_INFO]
-
script_name = env['SCRIPT_NAME']
-
hHost = env['HTTP_HOST']
-
sName = env['SERVER_NAME']
-
sPort = env['SERVER_PORT']
-
-
@mapping.each do |host, location, match, app|
-
unless casecmp?(hHost, host) \
-
|| casecmp?(sName, host) \
-
|| (!host && (casecmp?(hHost, sName) ||
-
casecmp?(hHost, sName+':'+sPort)))
-
next
-
end
-
-
next unless m = match.match(path.to_s)
-
-
rest = m[1]
-
next unless !rest || rest.empty? || rest[0] == ?/
-
-
env['SCRIPT_NAME'] = (script_name + location)
-
env['PATH_INFO'] = rest
-
-
return app.call(env)
-
end
-
-
[404, {CONTENT_TYPE => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
-
-
ensure
-
env['PATH_INFO'] = path
-
env['SCRIPT_NAME'] = script_name
-
end
-
-
1
private
-
1
def casecmp?(v1, v2)
-
# if both nil, or they're the same string
-
return true if v1 == v2
-
-
# if either are nil... (but they're not the same)
-
return false if v1.nil?
-
return false if v2.nil?
-
-
# otherwise check they're not case-insensitive the same
-
v1.casecmp(v2).zero?
-
end
-
end
-
end
-
-
1
require 'active_support/dependencies/autoload'
-
1
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/html-scanner"
-
-
1
module HTML
-
1
extend ActiveSupport::Autoload
-
-
1
eager_autoload do
-
1
autoload :CDATA, 'html/node'
-
1
autoload :Document, 'html/document'
-
1
autoload :FullSanitizer, 'html/sanitizer'
-
1
autoload :LinkSanitizer, 'html/sanitizer'
-
1
autoload :Node, 'html/node'
-
1
autoload :Sanitizer, 'html/sanitizer'
-
1
autoload :Selector, 'html/selector'
-
1
autoload :Tag, 'html/node'
-
1
autoload :Text, 'html/node'
-
1
autoload :Tokenizer, 'html/tokenizer'
-
1
autoload :Version, 'html/version'
-
1
autoload :WhiteListSanitizer, 'html/sanitizer'
-
end
-
end
-
1
require 'rails/dom/testing/assertions'
-
1
require 'active_support/concern'
-
1
require 'nokogiri'
-
-
1
module Rails
-
1
module Dom
-
1
module Testing
-
1
module Assertions
-
1
autoload :DomAssertions, 'rails/dom/testing/assertions/dom_assertions'
-
1
autoload :SelectorAssertions, 'rails/dom/testing/assertions/selector_assertions'
-
1
autoload :TagAssertions, 'rails/dom/testing/assertions/tag_assertions'
-
-
1
extend ActiveSupport::Concern
-
-
1
include DomAssertions
-
1
include SelectorAssertions
-
1
include TagAssertions
-
end
-
end
-
end
-
end
-
1
module Rails
-
1
module Dom
-
1
module Testing
-
1
module Assertions
-
1
module DomAssertions
-
# \Test two HTML strings for equivalency (e.g., equal even when attributes are in another order)
-
#
-
# # assert that the referenced method generates the appropriate HTML string
-
# assert_dom_equal '<a href="http://www.example.com">Apples</a>', link_to("Apples", "http://www.example.com")
-
1
def assert_dom_equal(expected, actual, message = nil)
-
expected_dom, actual_dom = fragment(expected), fragment(actual)
-
message ||= "Expected: #{expected}\nActual: #{actual}"
-
assert compare_doms(expected_dom, actual_dom), message
-
end
-
-
# The negated form of +assert_dom_equal+.
-
#
-
# # assert that the referenced method does not generate the specified HTML string
-
# assert_dom_not_equal '<a href="http://www.example.com">Apples</a>', link_to("Oranges", "http://www.example.com")
-
1
def assert_dom_not_equal(expected, actual, message = nil)
-
expected_dom, actual_dom = fragment(expected), fragment(actual)
-
message ||= "Expected: #{expected}\nActual: #{actual}"
-
assert_not compare_doms(expected_dom, actual_dom), message
-
end
-
-
1
protected
-
-
1
def compare_doms(expected, actual)
-
return false unless expected.children.size == actual.children.size
-
-
expected.children.each_with_index do |child, i|
-
return false unless equal_children?(child, actual.children[i])
-
end
-
-
true
-
end
-
-
1
def equal_children?(child, other_child)
-
return false unless child.type == other_child.type
-
-
if child.element?
-
child.name == other_child.name &&
-
equal_attribute_nodes?(child.attribute_nodes, other_child.attribute_nodes) &&
-
compare_doms(child, other_child)
-
else
-
child.to_s == other_child.to_s
-
end
-
end
-
-
1
def equal_attribute_nodes?(nodes, other_nodes)
-
return false unless nodes.size == other_nodes.size
-
-
nodes = nodes.sort_by(&:name)
-
other_nodes = other_nodes.sort_by(&:name)
-
-
nodes.each_with_index do |attr, i|
-
return false unless equal_attribute?(attr, other_nodes[i])
-
end
-
-
true
-
end
-
-
1
def equal_attribute?(attr, other_attr)
-
attr.name == other_attr.name && attr.value == other_attr.value
-
end
-
-
1
private
-
-
1
def fragment(text)
-
Nokogiri::HTML::DocumentFragment.parse(text)
-
end
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/deprecation'
-
1
require 'rails/deprecated_sanitizer/html-scanner'
-
-
1
module Rails
-
1
module Dom
-
1
module Testing
-
1
module Assertions
-
# Pair of assertions to testing elements in the HTML output of the response.
-
1
module TagAssertions
-
# Asserts that there is a tag/node/element in the body of the response
-
# that meets all of the given conditions. The +conditions+ parameter must
-
# be a hash of any of the following keys (all are optional):
-
#
-
# * <tt>:tag</tt>: the node type must match the corresponding value
-
# * <tt>:attributes</tt>: a hash. The node's attributes must match the
-
# corresponding values in the hash.
-
# * <tt>:parent</tt>: a hash. The node's parent must match the
-
# corresponding hash.
-
# * <tt>:child</tt>: a hash. At least one of the node's immediate children
-
# must meet the criteria described by the hash.
-
# * <tt>:ancestor</tt>: a hash. At least one of the node's ancestors must
-
# meet the criteria described by the hash.
-
# * <tt>:descendant</tt>: a hash. At least one of the node's descendants
-
# must meet the criteria described by the hash.
-
# * <tt>:sibling</tt>: a hash. At least one of the node's siblings must
-
# meet the criteria described by the hash.
-
# * <tt>:after</tt>: a hash. The node must be after any sibling meeting
-
# the criteria described by the hash, and at least one sibling must match.
-
# * <tt>:before</tt>: a hash. The node must be before any sibling meeting
-
# the criteria described by the hash, and at least one sibling must match.
-
# * <tt>:children</tt>: a hash, for counting children of a node. Accepts
-
# the keys:
-
# * <tt>:count</tt>: either a number or a range which must equal (or
-
# include) the number of children that match.
-
# * <tt>:less_than</tt>: the number of matching children must be less
-
# than this number.
-
# * <tt>:greater_than</tt>: the number of matching children must be
-
# greater than this number.
-
# * <tt>:only</tt>: another hash consisting of the keys to use
-
# to match on the children, and only matching children will be
-
# counted.
-
# * <tt>:content</tt>: the textual content of the node must match the
-
# given value. This will not match HTML tags in the body of a
-
# tag--only text.
-
#
-
# Conditions are matched using the following algorithm:
-
#
-
# * if the condition is a string, it must be a substring of the value.
-
# * if the condition is a regexp, it must match the value.
-
# * if the condition is a number, the value must match number.to_s.
-
# * if the condition is +true+, the value must not be +nil+.
-
# * if the condition is +false+ or +nil+, the value must be +nil+.
-
#
-
# # Assert that there is a "span" tag
-
# assert_tag tag: "span"
-
#
-
# # Assert that there is a "span" tag with id="x"
-
# assert_tag tag: "span", attributes: { id: "x" }
-
#
-
# # Assert that there is a "span" tag using the short-hand
-
# assert_tag :span
-
#
-
# # Assert that there is a "span" tag with id="x" using the short-hand
-
# assert_tag :span, attributes: { id: "x" }
-
#
-
# # Assert that there is a "span" inside of a "div"
-
# assert_tag tag: "span", parent: { tag: "div" }
-
#
-
# # Assert that there is a "span" somewhere inside a table
-
# assert_tag tag: "span", ancestor: { tag: "table" }
-
#
-
# # Assert that there is a "span" with at least one "em" child
-
# assert_tag tag: "span", child: { tag: "em" }
-
#
-
# # Assert that there is a "span" containing a (possibly nested)
-
# # "strong" tag.
-
# assert_tag tag: "span", descendant: { tag: "strong" }
-
#
-
# # Assert that there is a "span" containing between 2 and 4 "em" tags
-
# # as immediate children
-
# assert_tag tag: "span",
-
# children: { count: 2..4, only: { tag: "em" } }
-
#
-
# # Get funky: assert that there is a "div", with an "ul" ancestor
-
# # and an "li" parent (with "class" = "enum"), and containing a
-
# # "span" descendant that contains text matching /hello world/
-
# assert_tag tag: "div",
-
# ancestor: { tag: "ul" },
-
# parent: { tag: "li",
-
# attributes: { class: "enum" } },
-
# descendant: { tag: "span",
-
# child: /hello world/ }
-
#
-
# <b>Please note</b>: +assert_tag+ and +assert_no_tag+ only work
-
# with well-formed XHTML. They recognize a few tags as implicitly self-closing
-
# (like br and hr and such) but will not work correctly with tags
-
# that allow optional closing tags (p, li, td). <em>You must explicitly
-
# close all of your tags to use these assertions.</em>
-
1
def assert_tag(*opts)
-
ActiveSupport::Deprecation.warn("assert_tag is deprecated and will be removed at Rails 5. Use assert_select to get the same feature.")
-
-
opts = opts.size > 1 ? opts.last.merge({ tag: opts.first.to_s }) : opts.first
-
tag = _find_tag(opts)
-
-
assert tag, "expected tag, but no tag found matching #{opts.inspect} in:\n#{@response.body.inspect}"
-
end
-
-
# Identical to +assert_tag+, but asserts that a matching tag does _not_
-
# exist. (See +assert_tag+ for a full discussion of the syntax.)
-
#
-
# # Assert that there is not a "div" containing a "p"
-
# assert_no_tag tag: "div", descendant: { tag: "p" }
-
#
-
# # Assert that an unordered list is empty
-
# assert_no_tag tag: "ul", descendant: { tag: "li" }
-
#
-
# # Assert that there is not a "p" tag with between 1 to 3 "img" tags
-
# # as immediate children
-
# assert_no_tag tag: "p",
-
# children: { count: 1..3, only: { tag: "img" } }
-
1
def assert_no_tag(*opts)
-
ActiveSupport::Deprecation.warn("assert_no_tag is deprecated and will be removed at Rails 5. Use assert_select to get the same feature.")
-
-
opts = opts.size > 1 ? opts.last.merge({ tag: opts.first.to_s }) : opts.first
-
tag = _find_tag(opts)
-
-
assert !tag, "expected no tag, but found tag matching #{opts.inspect} in:\n#{@response.body.inspect}"
-
end
-
-
1
def find_tag(conditions)
-
ActiveSupport::Deprecation.warn("find_tag is deprecated and will be removed at Rails 5 without replacement.")
-
-
_find_tag(conditions)
-
end
-
-
1
def find_all_tag(conditions)
-
ActiveSupport::Deprecation.warn("find_all_tag is deprecated and will be removed at Rails 5 without replacement. Use assert_select to get the same feature.")
-
-
html_scanner_document.find_all(conditions)
-
end
-
-
1
private
-
1
def _find_tag(conditions)
-
html_scanner_document.find(conditions)
-
end
-
-
1
def html_scanner_document
-
xml = @response.content_type =~ /xml$/
-
@html_scanner_document ||= HTML::Document.new(@response.body, false, xml)
-
end
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/backtrace_cleaner'
-
-
1
module Rails
-
1
class BacktraceCleaner < ActiveSupport::BacktraceCleaner
-
1
APP_DIRS_PATTERN = /^\/?(app|config|lib|test)/
-
1
RENDER_TEMPLATE_PATTERN = /:in `_render_template_\w*'/
-
1
EMPTY_STRING = ''.freeze
-
1
SLASH = '/'.freeze
-
1
DOT_SLASH = './'.freeze
-
-
1
def initialize
-
1
super
-
1
@root = "#{Rails.root}/".freeze
-
1
add_filter { |line| line.sub(@root, EMPTY_STRING) }
-
1
add_filter { |line| line.sub(RENDER_TEMPLATE_PATTERN, EMPTY_STRING) }
-
1
add_filter { |line| line.sub(DOT_SLASH, SLASH) } # for tests
-
-
1
add_gem_filters
-
1
add_silencer { |line| line !~ APP_DIRS_PATTERN }
-
end
-
-
1
private
-
1
def add_gem_filters
-
3
gems_paths = (Gem.path | [Gem.default_dir]).map { |p| Regexp.escape(p) }
-
1
return if gems_paths.empty?
-
-
1
gems_regexp = %r{(#{gems_paths.join('|')})/gems/([^/]+)-([\w.]+)/(.*)}
-
1
gems_result = '\2 (\3) \4'.freeze
-
1
add_filter { |line| line.sub(gems_regexp, gems_result) }
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_core "formatters/helpers"
-
1
require 'stringio'
-
-
1
module RSpec
-
1
module Core
-
1
module Formatters
-
# RSpec's built-in formatters are all subclasses of
-
# RSpec::Core::Formatters::BaseTextFormatter.
-
#
-
# @see RSpec::Core::Formatters::BaseTextFormatter
-
# @see RSpec::Core::Reporter
-
# @see RSpec::Core::Formatters::Protocol
-
1
class BaseFormatter
-
# All formatters inheriting from this formatter will receive these
-
# notifications.
-
1
Formatters.register self, :start, :example_group_started, :close
-
1
attr_accessor :example_group
-
1
attr_reader :output
-
-
# @api public
-
# @param output [IO] the formatter output
-
# @see RSpec::Core::Formatters::Protocol#initialize
-
1
def initialize(output)
-
1
@output = output || StringIO.new
-
1
@example_group = nil
-
end
-
-
# @api public
-
#
-
# @param notification [StartNotification]
-
# @see RSpec::Core::Formatters::Protocol#start
-
1
def start(notification)
-
1
start_sync_output
-
1
@example_count = notification.count
-
end
-
-
# @api public
-
#
-
# @param notification [GroupNotification] containing example_group
-
# subclass of `RSpec::Core::ExampleGroup`
-
# @see RSpec::Core::Formatters::Protocol#example_group_started
-
1
def example_group_started(notification)
-
25
@example_group = notification.group
-
end
-
-
# @api public
-
#
-
# @param _notification [NullNotification] (Ignored)
-
# @see RSpec::Core::Formatters::Protocol#close
-
1
def close(_notification)
-
restore_sync_output
-
end
-
-
1
private
-
-
1
def start_sync_output
-
1
@old_sync, output.sync = output.sync, true if output_supports_sync
-
end
-
-
1
def restore_sync_output
-
output.sync = @old_sync if output_supports_sync && !output.closed?
-
end
-
-
1
def output_supports_sync
-
1
output.respond_to?(:sync=)
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_core "formatters/base_formatter"
-
1
RSpec::Support.require_rspec_core "formatters/console_codes"
-
-
1
module RSpec
-
1
module Core
-
1
module Formatters
-
# Base for all of RSpec's built-in formatters. See
-
# RSpec::Core::Formatters::BaseFormatter to learn more about all of the
-
# methods called by the reporter.
-
#
-
# @see RSpec::Core::Formatters::BaseFormatter
-
# @see RSpec::Core::Reporter
-
1
class BaseTextFormatter < BaseFormatter
-
1
Formatters.register self,
-
:message, :dump_summary, :dump_failures, :dump_pending, :seed
-
-
# @api public
-
#
-
# Used by the reporter to send messages to the output stream.
-
#
-
# @param notification [MessageNotification] containing message
-
1
def message(notification)
-
output.puts notification.message
-
end
-
-
# @api public
-
#
-
# Dumps detailed information about each example failure.
-
#
-
# @param notification [NullNotification]
-
1
def dump_failures(notification)
-
1
return if notification.failure_notifications.empty?
-
output.puts notification.fully_formatted_failed_examples
-
end
-
-
# @api public
-
#
-
# This method is invoked after the dumping of examples and failures.
-
# Each parameter is assigned to a corresponding attribute.
-
#
-
# @param summary [SummaryNotification] containing duration,
-
# example_count, failure_count and pending_count
-
1
def dump_summary(summary)
-
1
output.puts summary.fully_formatted
-
end
-
-
# @private
-
1
def dump_pending(notification)
-
1
return if notification.pending_examples.empty?
-
output.puts notification.fully_formatted_pending_examples
-
end
-
-
# @private
-
1
def seed(notification)
-
2
return unless notification.seed_used?
-
2
output.puts notification.fully_formatted
-
end
-
-
# @api public
-
#
-
# Invoked at the very end, `close` allows the formatter to clean
-
# up resources, e.g. open streams, etc.
-
#
-
# @param _notification [NullNotification] (Ignored)
-
1
def close(_notification)
-
1
return unless IO === output
-
1
return if output.closed?
-
-
1
output.puts
-
-
1
output.flush
-
1
output.close unless output == $stdout
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Core
-
1
module Formatters
-
# ConsoleCodes provides helpers for formatting console output
-
# with ANSI codes, e.g. color's and bold.
-
1
module ConsoleCodes
-
# @private
-
1
VT100_CODES =
-
{
-
:black => 30,
-
:red => 31,
-
:green => 32,
-
:yellow => 33,
-
:blue => 34,
-
:magenta => 35,
-
:cyan => 36,
-
:white => 37,
-
:bold => 1,
-
}
-
# @private
-
1
VT100_CODE_VALUES = VT100_CODES.invert
-
-
1
module_function
-
-
# @private
-
1
CONFIG_COLORS_TO_METHODS = Configuration.instance_methods.grep(/_color\z/).inject({}) do |hash, method|
-
6
hash[method.to_s.sub(/_color\z/, '').to_sym] = method
-
6
hash
-
end
-
-
# Fetches the correct code for the supplied symbol, or checks
-
# that a code is valid. Defaults to white (37).
-
#
-
# @param code_or_symbol [Symbol, Fixnum] Symbol or code to check
-
# @return [Fixnum] a console code
-
1
def console_code_for(code_or_symbol)
-
if (config_method = CONFIG_COLORS_TO_METHODS[code_or_symbol])
-
console_code_for RSpec.configuration.__send__(config_method)
-
elsif VT100_CODE_VALUES.key?(code_or_symbol)
-
code_or_symbol
-
else
-
VT100_CODES.fetch(code_or_symbol) do
-
console_code_for(:white)
-
end
-
end
-
end
-
-
# Wraps a piece of text in ANSI codes with the supplied code. Will
-
# only apply the control code if `RSpec.configuration.color_enabled?`
-
# returns true.
-
#
-
# @param text [String] the text to wrap
-
# @param code_or_symbol [Symbol, Fixnum] the desired control code
-
# @return [String] the wrapped text
-
1
def wrap(text, code_or_symbol)
-
29
if RSpec.configuration.color_enabled?
-
"\e[#{console_code_for(code_or_symbol)}m#{text}\e[0m"
-
else
-
29
text
-
end
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_core "formatters/base_text_formatter"
-
-
1
module RSpec
-
1
module Core
-
1
module Formatters
-
# @private
-
1
class ProgressFormatter < BaseTextFormatter
-
1
Formatters.register self, :example_passed, :example_pending, :example_failed, :start_dump
-
-
1
def example_passed(_notification)
-
28
output.print ConsoleCodes.wrap('.', :success)
-
end
-
-
1
def example_pending(_notification)
-
output.print ConsoleCodes.wrap('*', :pending)
-
end
-
-
1
def example_failed(_notification)
-
output.print ConsoleCodes.wrap('F', :failure)
-
end
-
-
1
def start_dump(_notification)
-
1
output.puts
-
end
-
end
-
end
-
end
-
end
-
1
require 'rspec/mocks'
-
-
1
module RSpec
-
1
module Core
-
1
module MockingAdapters
-
# @private
-
1
module RSpec
-
1
include ::RSpec::Mocks::ExampleMethods
-
-
1
def self.framework_name
-
1
:rspec
-
end
-
-
1
def self.configuration
-
::RSpec::Mocks.configuration
-
end
-
-
1
def setup_mocks_for_rspec
-
28
::RSpec::Mocks.setup
-
end
-
-
1
def verify_mocks_for_rspec
-
28
::RSpec::Mocks.verify
-
end
-
-
1
def teardown_mocks_for_rspec
-
28
::RSpec::Mocks.teardown
-
end
-
end
-
end
-
end
-
end
-
1
require 'rspec/support'
-
1
RSpec::Support.require_rspec_support "caller_filter"
-
1
RSpec::Support.require_rspec_support "warnings"
-
1
RSpec::Support.require_rspec_support "object_formatter"
-
-
1
require 'rspec/matchers'
-
-
7
RSpec::Support.define_optimized_require_for_rspec(:expectations) { |f| require_relative(f) }
-
-
%w[
-
expectation_target
-
configuration
-
fail_with
-
handler
-
version
-
6
].each { |file| RSpec::Support.require_rspec_expectations(file) }
-
-
1
module RSpec
-
# RSpec::Expectations provides a simple, readable API to express
-
# the expected outcomes in a code example. To express an expected
-
# outcome, wrap an object or block in `expect`, call `to` or `to_not`
-
# (aliased as `not_to`) and pass it a matcher object:
-
#
-
# expect(order.total).to eq(Money.new(5.55, :USD))
-
# expect(list).to include(user)
-
# expect(message).not_to match(/foo/)
-
# expect { do_something }.to raise_error
-
#
-
# The last form (the block form) is needed to match against ruby constructs
-
# that are not objects, but can only be observed when executing a block
-
# of code. This includes raising errors, throwing symbols, yielding,
-
# and changing values.
-
#
-
# When `expect(...).to` is invoked with a matcher, it turns around
-
# and calls `matcher.matches?(<object wrapped by expect>)`. For example,
-
# in the expression:
-
#
-
# expect(order.total).to eq(Money.new(5.55, :USD))
-
#
-
# ...`eq(Money.new(5.55, :USD))` returns a matcher object, and it results
-
# in the equivalent of `eq.matches?(order.total)`. If `matches?` returns
-
# `true`, the expectation is met and execution continues. If `false`, then
-
# the spec fails with the message returned by `eq.failure_message`.
-
#
-
# Given the expression:
-
#
-
# expect(order.entries).not_to include(entry)
-
#
-
# ...the `not_to` method (also available as `to_not`) invokes the equivalent of
-
# `include.matches?(order.entries)`, but it interprets `false` as success, and
-
# `true` as a failure, using the message generated by
-
# `include.failure_message_when_negated`.
-
#
-
# rspec-expectations ships with a standard set of useful matchers, and writing
-
# your own matchers is quite simple.
-
#
-
# See [RSpec::Matchers](../RSpec/Matchers) for more information about the
-
# built-in matchers that ship with rspec-expectations, and how to write your
-
# own custom matchers.
-
1
module Expectations
-
# Exception raised when an expectation fails.
-
#
-
# @note We subclass Exception so that in a stub implementation if
-
# the user sets an expectation, it can't be caught in their
-
# code by a bare `rescue`.
-
# @api public
-
1
class ExpectationNotMetError < Exception
-
end
-
-
# Exception raised from `aggregate_failures` when multiple expectations fail.
-
#
-
# @note The constant is defined here but the extensive logic of this class
-
# is lazily defined when `FailureAggregator` is autoloaded, since we do
-
# not need to waste time defining that functionality unless
-
# `aggregate_failures` is used.
-
1
class MultipleExpectationsNotMetError < ExpectationNotMetError
-
end
-
-
1
autoload :FailureAggregator, "rspec/expectations/failure_aggregator"
-
end
-
end
-
1
RSpec::Support.require_rspec_expectations "syntax"
-
-
1
module RSpec
-
1
module Expectations
-
# Provides configuration options for rspec-expectations.
-
# If you are using rspec-core, you can access this via a
-
# block passed to `RSpec::Core::Configuration#expect_with`.
-
# Otherwise, you can access it via RSpec::Expectations.configuration.
-
#
-
# @example
-
# RSpec.configure do |rspec|
-
# rspec.expect_with :rspec do |c|
-
# # c is the config object
-
# end
-
# end
-
#
-
# # or
-
#
-
# RSpec::Expectations.configuration
-
1
class Configuration
-
1
def initialize
-
1
@warn_about_potential_false_positives = true
-
end
-
-
# Configures the supported syntax.
-
# @param [Array<Symbol>, Symbol] values the syntaxes to enable
-
# @example
-
# RSpec.configure do |rspec|
-
# rspec.expect_with :rspec do |c|
-
# c.syntax = :should
-
# # or
-
# c.syntax = :expect
-
# # or
-
# c.syntax = [:should, :expect]
-
# end
-
# end
-
1
def syntax=(values)
-
1
if Array(values).include?(:expect)
-
1
Expectations::Syntax.enable_expect
-
else
-
Expectations::Syntax.disable_expect
-
end
-
-
1
if Array(values).include?(:should)
-
1
Expectations::Syntax.enable_should
-
else
-
Expectations::Syntax.disable_should
-
end
-
end
-
-
# The list of configured syntaxes.
-
# @return [Array<Symbol>] the list of configured syntaxes.
-
# @example
-
# unless RSpec::Matchers.configuration.syntax.include?(:expect)
-
# raise "this RSpec extension gem requires the rspec-expectations `:expect` syntax"
-
# end
-
1
def syntax
-
1
syntaxes = []
-
1
syntaxes << :should if Expectations::Syntax.should_enabled?
-
1
syntaxes << :expect if Expectations::Syntax.expect_enabled?
-
1
syntaxes
-
end
-
-
1
if ::RSpec.respond_to?(:configuration)
-
1
def color?
-
::RSpec.configuration.color_enabled?
-
end
-
else
-
# Indicates whether or not diffs should be colored.
-
# Delegates to rspec-core's color option if rspec-core
-
# is loaded; otherwise you can set it here.
-
attr_writer :color
-
-
# Indicates whether or not diffs should be colored.
-
# Delegates to rspec-core's color option if rspec-core
-
# is loaded; otherwise you can set it here.
-
def color?
-
defined?(@color) && @color
-
end
-
end
-
-
# Adds `should` and `should_not` to the given classes
-
# or modules. This can be used to ensure `should` works
-
# properly on things like proxy objects (particular
-
# `Delegator`-subclassed objects on 1.8).
-
#
-
# @param [Array<Module>] modules the list of classes or modules
-
# to add `should` and `should_not` to.
-
1
def add_should_and_should_not_to(*modules)
-
1
modules.each do |mod|
-
1
Expectations::Syntax.enable_should(mod)
-
end
-
end
-
-
# Sets or gets the backtrace formatter. The backtrace formatter should
-
# implement `#format_backtrace(Array<String>)`. This is used
-
# to format backtraces of errors handled by the `raise_error`
-
# matcher.
-
#
-
# If you are using rspec-core, rspec-core's backtrace formatting
-
# will be used (including respecting the presence or absence of
-
# the `--backtrace` option).
-
#
-
# @!attribute [rw] backtrace_formatter
-
1
attr_writer :backtrace_formatter
-
1
def backtrace_formatter
-
@backtrace_formatter ||= if defined?(::RSpec.configuration.backtrace_formatter)
-
::RSpec.configuration.backtrace_formatter
-
else
-
NullBacktraceFormatter
-
end
-
end
-
-
# Sets if custom matcher descriptions and failure messages
-
# should include clauses from methods defined using `chain`.
-
# @param value [Boolean]
-
1
attr_writer :include_chain_clauses_in_custom_matcher_descriptions
-
-
# Indicates whether or not custom matcher descriptions and failure messages
-
# should include clauses from methods defined using `chain`. It is
-
# false by default for backwards compatibility.
-
1
def include_chain_clauses_in_custom_matcher_descriptions?
-
@include_chain_clauses_in_custom_matcher_descriptions ||= false
-
end
-
-
# @private
-
1
def reset_syntaxes_to_default
-
1
self.syntax = [:should, :expect]
-
1
RSpec::Expectations::Syntax.warn_about_should!
-
end
-
-
# @api private
-
# Null implementation of a backtrace formatter used by default
-
# when rspec-core is not loaded. Does no filtering.
-
1
NullBacktraceFormatter = Module.new do
-
1
def self.format_backtrace(backtrace)
-
backtrace
-
end
-
end
-
-
# Configures whether RSpec will warn about matcher use which will
-
# potentially cause false positives in tests.
-
#
-
# @param value [Boolean]
-
1
attr_writer :warn_about_potential_false_positives
-
-
# Indicates whether RSpec will warn about matcher use which will
-
# potentially cause false positives in tests, generally you want to
-
# avoid such scenarios so this defaults to `true`.
-
1
def warn_about_potential_false_positives?
-
@warn_about_potential_false_positives
-
end
-
end
-
-
# The configuration object.
-
# @return [RSpec::Expectations::Configuration] the configuration object
-
1
def self.configuration
-
3
@configuration ||= Configuration.new
-
end
-
-
# set default syntax
-
1
configuration.reset_syntaxes_to_default
-
end
-
end
-
1
module RSpec
-
1
module Expectations
-
# Wraps the target of an expectation.
-
#
-
# @example
-
# expect(something) # => ExpectationTarget wrapping something
-
# expect { do_something } # => ExpectationTarget wrapping the block
-
#
-
# # used with `to`
-
# expect(actual).to eq(3)
-
#
-
# # with `not_to`
-
# expect(actual).not_to eq(3)
-
#
-
# @note `ExpectationTarget` is not intended to be instantiated
-
# directly by users. Use `expect` instead.
-
1
class ExpectationTarget
-
# @private
-
# Used as a sentinel value to be able to tell when the user
-
# did not pass an argument. We can't use `nil` for that because
-
# `nil` is a valid value to pass.
-
1
UndefinedValue = Module.new
-
-
# @api private
-
1
def initialize(value)
-
12
@target = value
-
end
-
-
# @private
-
1
def self.for(value, block)
-
12
if UndefinedValue.equal?(value)
-
unless block
-
raise ArgumentError, "You must pass either an argument or a block to `expect`."
-
end
-
BlockExpectationTarget.new(block)
-
12
elsif block
-
raise ArgumentError, "You cannot pass both an argument and a block to `expect`."
-
else
-
12
new(value)
-
end
-
end
-
-
# Runs the given expectation, passing if `matcher` returns true.
-
# @example
-
# expect(value).to eq(5)
-
# expect { perform }.to raise_error
-
# @param [Matcher]
-
# matcher
-
# @param [String or Proc] message optional message to display when the expectation fails
-
# @return [Boolean] true if the expectation succeeds (else raises)
-
# @see RSpec::Matchers
-
1
def to(matcher=nil, message=nil, &block)
-
12
prevent_operator_matchers(:to) unless matcher
-
12
RSpec::Expectations::PositiveExpectationHandler.handle_matcher(@target, matcher, message, &block)
-
end
-
-
# Runs the given expectation, passing if `matcher` returns false.
-
# @example
-
# expect(value).not_to eq(5)
-
# @param [Matcher]
-
# matcher
-
# @param [String or Proc] message optional message to display when the expectation fails
-
# @return [Boolean] false if the negative expectation succeeds (else raises)
-
# @see RSpec::Matchers
-
1
def not_to(matcher=nil, message=nil, &block)
-
prevent_operator_matchers(:not_to) unless matcher
-
RSpec::Expectations::NegativeExpectationHandler.handle_matcher(@target, matcher, message, &block)
-
end
-
1
alias to_not not_to
-
-
1
private
-
-
1
def prevent_operator_matchers(verb)
-
raise ArgumentError, "The expect syntax does not support operator matchers, " \
-
"so you must pass a matcher to `##{verb}`."
-
end
-
end
-
-
# @private
-
# Validates the provided matcher to ensure it supports block
-
# expectations, in order to avoid user confusion when they
-
# use a block thinking the expectation will be on the return
-
# value of the block rather than the block itself.
-
1
class BlockExpectationTarget < ExpectationTarget
-
1
def to(matcher, message=nil, &block)
-
enforce_block_expectation(matcher)
-
super
-
end
-
-
1
def not_to(matcher, message=nil, &block)
-
enforce_block_expectation(matcher)
-
super
-
end
-
1
alias to_not not_to
-
-
1
private
-
-
1
def enforce_block_expectation(matcher)
-
return if supports_block_expectations?(matcher)
-
-
raise ExpectationNotMetError, "You must pass an argument rather than a block to use the provided " \
-
"matcher (#{RSpec::Support::ObjectFormatter.format(matcher)}), or the matcher must implement " \
-
"`supports_block_expectations?`."
-
end
-
-
1
def supports_block_expectations?(matcher)
-
matcher.supports_block_expectations?
-
rescue NoMethodError
-
false
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Expectations
-
1
class << self
-
# @private
-
1
def differ
-
RSpec::Support::Differ.new(
-
:object_preparer => lambda { |object| RSpec::Matchers::Composable.surface_descriptions_in(object) },
-
:color => RSpec::Matchers.configuration.color?
-
)
-
end
-
-
# Raises an RSpec::Expectations::ExpectationNotMetError with message.
-
# @param [String] message
-
# @param [Object] expected
-
# @param [Object] actual
-
#
-
# Adds a diff to the failure message when `expected` and `actual` are
-
# both present.
-
1
def fail_with(message, expected=nil, actual=nil)
-
unless message
-
raise ArgumentError, "Failure message is nil. Does your matcher define the " \
-
"appropriate failure_message[_when_negated] method to return a string?"
-
end
-
-
message = ::RSpec::Matchers::ExpectedsForMultipleDiffs.from(expected).message_with_diff(message, differ, actual)
-
-
RSpec::Support.notify_failure(RSpec::Expectations::ExpectationNotMetError.new message)
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Expectations
-
# @private
-
1
module ExpectationHelper
-
1
def self.check_message(msg)
-
23
unless msg.nil? || msg.respond_to?(:to_str) || msg.respond_to?(:call)
-
::Kernel.warn [
-
"WARNING: ignoring the provided expectation message argument (",
-
msg.inspect,
-
") since it is not a string or a proc."
-
].join
-
end
-
end
-
-
# Returns an RSpec-3+ compatible matcher, wrapping a legacy one
-
# in an adapter if necessary.
-
#
-
# @private
-
1
def self.modern_matcher_from(matcher)
-
LegacyMatcherAdapter::RSpec2.wrap(matcher) ||
-
23
LegacyMatcherAdapter::RSpec1.wrap(matcher) || matcher
-
end
-
-
1
def self.with_matcher(handler, matcher, message)
-
23
check_message(message)
-
23
matcher = modern_matcher_from(matcher)
-
23
yield matcher
-
ensure
-
23
::RSpec::Matchers.last_expectation_handler = handler
-
23
::RSpec::Matchers.last_matcher = matcher
-
end
-
-
1
def self.handle_failure(matcher, message, failure_message_method)
-
message = message.call if message.respond_to?(:call)
-
message ||= matcher.__send__(failure_message_method)
-
-
if matcher.respond_to?(:diffable?) && matcher.diffable?
-
::RSpec::Expectations.fail_with message, matcher.expected, matcher.actual
-
else
-
::RSpec::Expectations.fail_with message
-
end
-
end
-
end
-
-
# @private
-
1
class PositiveExpectationHandler
-
1
def self.handle_matcher(actual, initial_matcher, message=nil, &block)
-
15
ExpectationHelper.with_matcher(self, initial_matcher, message) do |matcher|
-
15
return ::RSpec::Matchers::BuiltIn::PositiveOperatorMatcher.new(actual) unless initial_matcher
-
15
matcher.matches?(actual, &block) || ExpectationHelper.handle_failure(matcher, message, :failure_message)
-
end
-
end
-
-
1
def self.verb
-
1
"should"
-
end
-
-
1
def self.should_method
-
:should
-
end
-
-
1
def self.opposite_should_method
-
:should_not
-
end
-
end
-
-
# @private
-
1
class NegativeExpectationHandler
-
1
def self.handle_matcher(actual, initial_matcher, message=nil, &block)
-
8
ExpectationHelper.with_matcher(self, initial_matcher, message) do |matcher|
-
8
return ::RSpec::Matchers::BuiltIn::NegativeOperatorMatcher.new(actual) unless initial_matcher
-
8
!(does_not_match?(matcher, actual, &block) || ExpectationHelper.handle_failure(matcher, message, :failure_message_when_negated))
-
end
-
end
-
-
1
def self.does_not_match?(matcher, actual, &block)
-
8
if matcher.respond_to?(:does_not_match?)
-
matcher.does_not_match?(actual, &block)
-
else
-
8
!matcher.matches?(actual, &block)
-
end
-
end
-
-
1
def self.verb
-
"should not"
-
end
-
-
1
def self.should_method
-
:should_not
-
end
-
-
1
def self.opposite_should_method
-
:should
-
end
-
end
-
-
# Wraps a matcher written against one of the legacy protocols in
-
# order to present the current protocol.
-
#
-
# @private
-
1
class LegacyMatcherAdapter < Matchers::MatcherDelegator
-
1
def initialize(matcher)
-
super
-
::RSpec.warn_deprecation(<<-EOS.gsub(/^\s+\|/, ''), :type => "legacy_matcher")
-
|#{matcher.class.name || matcher.inspect} implements a legacy RSpec matcher
-
|protocol. For the current protocol you should expose the failure messages
-
|via the `failure_message` and `failure_message_when_negated` methods.
-
|(Used from #{CallerFilter.first_non_rspec_line})
-
EOS
-
end
-
-
1
def self.wrap(matcher)
-
46
new(matcher) if interface_matches?(matcher)
-
end
-
-
# Starting in RSpec 1.2 (and continuing through all 2.x releases),
-
# the failure message protocol was:
-
# * `failure_message_for_should`
-
# * `failure_message_for_should_not`
-
# @private
-
1
class RSpec2 < self
-
1
def failure_message
-
base_matcher.failure_message_for_should
-
end
-
-
1
def failure_message_when_negated
-
base_matcher.failure_message_for_should_not
-
end
-
-
1
def self.interface_matches?(matcher)
-
(
-
!matcher.respond_to?(:failure_message) &&
-
23
matcher.respond_to?(:failure_message_for_should)
-
) || (
-
!matcher.respond_to?(:failure_message_when_negated) &&
-
23
matcher.respond_to?(:failure_message_for_should_not)
-
23
)
-
end
-
end
-
-
# Before RSpec 1.2, the failure message protocol was:
-
# * `failure_message`
-
# * `negative_failure_message`
-
# @private
-
1
class RSpec1 < self
-
1
def failure_message
-
base_matcher.failure_message
-
end
-
-
1
def failure_message_when_negated
-
base_matcher.negative_failure_message
-
end
-
-
# Note: `failure_message` is part of the RSpec 3 protocol
-
# (paired with `failure_message_when_negated`), so we don't check
-
# for `failure_message` here.
-
1
def self.interface_matches?(matcher)
-
!matcher.respond_to?(:failure_message_when_negated) &&
-
23
matcher.respond_to?(:negative_failure_message)
-
end
-
end
-
end
-
-
# RSpec 3.0 was released with the class name misspelled. For SemVer compatibility,
-
# we will provide this misspelled alias until 4.0.
-
# @deprecated Use LegacyMatcherAdapter instead.
-
# @private
-
1
LegacyMacherAdapter = LegacyMatcherAdapter
-
end
-
end
-
1
module RSpec
-
1
module Expectations
-
# @api private
-
# Provides methods for enabling and disabling the available
-
# syntaxes provided by rspec-expectations.
-
1
module Syntax
-
1
module_function
-
-
# @api private
-
# Determines where we add `should` and `should_not`.
-
1
def default_should_host
-
4
@default_should_host ||= ::Object.ancestors.last
-
end
-
-
# @api private
-
# Instructs rspec-expectations to warn on first usage of `should` or `should_not`.
-
# Enabled by default. This is largely here to facilitate testing.
-
1
def warn_about_should!
-
1
@warn_about_should = true
-
end
-
-
# @api private
-
# Generates a deprecation warning for the given method if no warning
-
# has already been issued.
-
1
def warn_about_should_unless_configured(method_name)
-
4
return unless @warn_about_should
-
-
1
RSpec.deprecate(
-
"Using `#{method_name}` from rspec-expectations' old `:should` syntax without explicitly enabling the syntax",
-
:replacement => "the new `:expect` syntax or explicitly enable `:should` with `config.expect_with(:rspec) { |c| c.syntax = :should }`"
-
)
-
-
1
@warn_about_should = false
-
end
-
-
# @api private
-
# Enables the `should` syntax.
-
1
def enable_should(syntax_host=default_should_host)
-
2
@warn_about_should = false if syntax_host == default_should_host
-
2
return if should_enabled?(syntax_host)
-
-
1
syntax_host.module_exec do
-
1
def should(matcher=nil, message=nil, &block)
-
3
::RSpec::Expectations::Syntax.warn_about_should_unless_configured(__method__)
-
3
::RSpec::Expectations::PositiveExpectationHandler.handle_matcher(self, matcher, message, &block)
-
end
-
-
1
def should_not(matcher=nil, message=nil, &block)
-
1
::RSpec::Expectations::Syntax.warn_about_should_unless_configured(__method__)
-
1
::RSpec::Expectations::NegativeExpectationHandler.handle_matcher(self, matcher, message, &block)
-
end
-
end
-
end
-
-
# @api private
-
# Disables the `should` syntax.
-
1
def disable_should(syntax_host=default_should_host)
-
return unless should_enabled?(syntax_host)
-
-
syntax_host.module_exec do
-
undef should
-
undef should_not
-
end
-
end
-
-
# @api private
-
# Enables the `expect` syntax.
-
1
def enable_expect(syntax_host=::RSpec::Matchers)
-
1
return if expect_enabled?(syntax_host)
-
-
1
syntax_host.module_exec do
-
1
def expect(value=::RSpec::Expectations::ExpectationTarget::UndefinedValue, &block)
-
12
::RSpec::Expectations::ExpectationTarget.for(value, block)
-
end
-
end
-
end
-
-
# @api private
-
# Disables the `expect` syntax.
-
1
def disable_expect(syntax_host=::RSpec::Matchers)
-
return unless expect_enabled?(syntax_host)
-
-
syntax_host.module_exec do
-
undef expect
-
end
-
end
-
-
# @api private
-
# Indicates whether or not the `should` syntax is enabled.
-
1
def should_enabled?(syntax_host=default_should_host)
-
3
syntax_host.method_defined?(:should)
-
end
-
-
# @api private
-
# Indicates whether or not the `expect` syntax is enabled.
-
1
def expect_enabled?(syntax_host=::RSpec::Matchers)
-
2
syntax_host.method_defined?(:expect)
-
end
-
end
-
end
-
end
-
-
1
if defined?(BasicObject)
-
# The legacy `:should` syntax adds the following methods directly to
-
# `BasicObject` so that they are available off of any object. Note, however,
-
# that this syntax does not always play nice with delegate/proxy objects.
-
# We recommend you use the non-monkeypatching `:expect` syntax instead.
-
1
class BasicObject
-
# @method should
-
# Passes if `matcher` returns true. Available on every `Object`.
-
# @example
-
# actual.should eq expected
-
# actual.should match /expression/
-
# @param [Matcher]
-
# matcher
-
# @param [String] message optional message to display when the expectation fails
-
# @return [Boolean] true if the expectation succeeds (else raises)
-
# @note This is only available when you have enabled the `:should` syntax.
-
# @see RSpec::Matchers
-
-
# @method should_not
-
# Passes if `matcher` returns false. Available on every `Object`.
-
# @example
-
# actual.should_not eq expected
-
# @param [Matcher]
-
# matcher
-
# @param [String] message optional message to display when the expectation fails
-
# @return [Boolean] false if the negative expectation succeeds (else raises)
-
# @note This is only available when you have enabled the `:should` syntax.
-
# @see RSpec::Matchers
-
end
-
end
-
1
module RSpec
-
1
module Expectations
-
# @private
-
1
module Version
-
1
STRING = '3.4.0'
-
end
-
end
-
end
-
1
require 'rspec/support'
-
1
RSpec::Support.require_rspec_support 'matcher_definition'
-
10
RSpec::Support.define_optimized_require_for_rspec(:matchers) { |f| require_relative(f) }
-
-
%w[
-
english_phrasing
-
composable
-
built_in
-
generated_descriptions
-
dsl
-
matcher_delegator
-
aliased_matcher
-
expecteds_for_multiple_diffs
-
9
].each { |file| RSpec::Support.require_rspec_matchers(file) }
-
-
# RSpec's top level namespace. All of rspec-expectations is contained
-
# in the `RSpec::Expectations` and `RSpec::Matchers` namespaces.
-
1
module RSpec
-
# RSpec::Matchers provides a number of useful matchers we use to define
-
# expectations. Any object that implements the [matcher protocol](Matchers/MatcherProtocol)
-
# can be used as a matcher.
-
#
-
# ## Predicates
-
#
-
# In addition to matchers that are defined explicitly, RSpec will create
-
# custom matchers on the fly for any arbitrary predicate, giving your specs a
-
# much more natural language feel.
-
#
-
# A Ruby predicate is a method that ends with a "?" and returns true or false.
-
# Common examples are `empty?`, `nil?`, and `instance_of?`.
-
#
-
# All you need to do is write `expect(..).to be_` followed by the predicate
-
# without the question mark, and RSpec will figure it out from there.
-
# For example:
-
#
-
# expect([]).to be_empty # => [].empty?() | passes
-
# expect([]).not_to be_empty # => [].empty?() | fails
-
#
-
# In addtion to prefixing the predicate matchers with "be_", you can also use "be_a_"
-
# and "be_an_", making your specs read much more naturally:
-
#
-
# expect("a string").to be_an_instance_of(String) # =>"a string".instance_of?(String) # passes
-
#
-
# expect(3).to be_a_kind_of(Fixnum) # => 3.kind_of?(Numeric) | passes
-
# expect(3).to be_a_kind_of(Numeric) # => 3.kind_of?(Numeric) | passes
-
# expect(3).to be_an_instance_of(Fixnum) # => 3.instance_of?(Fixnum) | passes
-
# expect(3).not_to be_an_instance_of(Numeric) # => 3.instance_of?(Numeric) | fails
-
#
-
# RSpec will also create custom matchers for predicates like `has_key?`. To
-
# use this feature, just state that the object should have_key(:key) and RSpec will
-
# call has_key?(:key) on the target. For example:
-
#
-
# expect(:a => "A").to have_key(:a)
-
# expect(:a => "A").to have_key(:b) # fails
-
#
-
# You can use this feature to invoke any predicate that begins with "has_", whether it is
-
# part of the Ruby libraries (like `Hash#has_key?`) or a method you wrote on your own class.
-
#
-
# Note that RSpec does not provide composable aliases for these dynamic predicate
-
# matchers. You can easily define your own aliases, though:
-
#
-
# RSpec::Matchers.alias_matcher :a_user_who_is_an_admin, :be_an_admin
-
# expect(user_list).to include(a_user_who_is_an_admin)
-
#
-
# ## Custom Matchers
-
#
-
# When you find that none of the stock matchers provide a natural feeling
-
# expectation, you can very easily write your own using RSpec's matcher DSL
-
# or writing one from scratch.
-
#
-
# ### Matcher DSL
-
#
-
# Imagine that you are writing a game in which players can be in various
-
# zones on a virtual board. To specify that bob should be in zone 4, you
-
# could say:
-
#
-
# expect(bob.current_zone).to eql(Zone.new("4"))
-
#
-
# But you might find it more expressive to say:
-
#
-
# expect(bob).to be_in_zone("4")
-
#
-
# and/or
-
#
-
# expect(bob).not_to be_in_zone("3")
-
#
-
# You can create such a matcher like so:
-
#
-
# RSpec::Matchers.define :be_in_zone do |zone|
-
# match do |player|
-
# player.in_zone?(zone)
-
# end
-
# end
-
#
-
# This will generate a <tt>be_in_zone</tt> method that returns a matcher
-
# with logical default messages for failures. You can override the failure
-
# messages and the generated description as follows:
-
#
-
# RSpec::Matchers.define :be_in_zone do |zone|
-
# match do |player|
-
# player.in_zone?(zone)
-
# end
-
#
-
# failure_message do |player|
-
# # generate and return the appropriate string.
-
# end
-
#
-
# failure_message_when_negated do |player|
-
# # generate and return the appropriate string.
-
# end
-
#
-
# description do
-
# # generate and return the appropriate string.
-
# end
-
# end
-
#
-
# Each of the message-generation methods has access to the block arguments
-
# passed to the <tt>create</tt> method (in this case, <tt>zone</tt>). The
-
# failure message methods (<tt>failure_message</tt> and
-
# <tt>failure_message_when_negated</tt>) are passed the actual value (the
-
# receiver of <tt>expect(..)</tt> or <tt>expect(..).not_to</tt>).
-
#
-
# ### Custom Matcher from scratch
-
#
-
# You could also write a custom matcher from scratch, as follows:
-
#
-
# class BeInZone
-
# def initialize(expected)
-
# @expected = expected
-
# end
-
#
-
# def matches?(target)
-
# @target = target
-
# @target.current_zone.eql?(Zone.new(@expected))
-
# end
-
#
-
# def failure_message
-
# "expected #{@target.inspect} to be in Zone #{@expected}"
-
# end
-
#
-
# def failure_message_when_negated
-
# "expected #{@target.inspect} not to be in Zone #{@expected}"
-
# end
-
# end
-
#
-
# ... and a method like this:
-
#
-
# def be_in_zone(expected)
-
# BeInZone.new(expected)
-
# end
-
#
-
# And then expose the method to your specs. This is normally done
-
# by including the method and the class in a module, which is then
-
# included in your spec:
-
#
-
# module CustomGameMatchers
-
# class BeInZone
-
# # ...
-
# end
-
#
-
# def be_in_zone(expected)
-
# # ...
-
# end
-
# end
-
#
-
# describe "Player behaviour" do
-
# include CustomGameMatchers
-
# # ...
-
# end
-
#
-
# or you can include in globally in a spec_helper.rb file <tt>require</tt>d
-
# from your spec file(s):
-
#
-
# RSpec::configure do |config|
-
# config.include(CustomGameMatchers)
-
# end
-
#
-
# ### Making custom matchers composable
-
#
-
# RSpec's built-in matchers are designed to be composed, in expressions like:
-
#
-
# expect(["barn", 2.45]).to contain_exactly(
-
# a_value_within(0.1).of(2.5),
-
# a_string_starting_with("bar")
-
# )
-
#
-
# Custom matchers can easily participate in composed matcher expressions like these.
-
# Include {RSpec::Matchers::Composable} in your custom matcher to make it support
-
# being composed (matchers defined using the DSL have this included automatically).
-
# Within your matcher's `matches?` method (or the `match` block, if using the DSL),
-
# use `values_match?(expected, actual)` rather than `expected == actual`.
-
# Under the covers, `values_match?` is able to match arbitrary
-
# nested data structures containing a mix of both matchers and non-matcher objects.
-
# It uses `===` and `==` to perform the matching, considering the values to
-
# match if either returns `true`. The `Composable` mixin also provides some helper
-
# methods for surfacing the matcher descriptions within your matcher's description
-
# or failure messages.
-
#
-
# RSpec's built-in matchers each have a number of aliases that rephrase the matcher
-
# from a verb phrase (such as `be_within`) to a noun phrase (such as `a_value_within`),
-
# which reads better when the matcher is passed as an argument in a composed matcher
-
# expressions, and also uses the noun-phrase wording in the matcher's `description`,
-
# for readable failure messages. You can alias your custom matchers in similar fashion
-
# using {RSpec::Matchers.alias_matcher}.
-
1
module Matchers
-
# @method expect
-
# Supports `expect(actual).to matcher` syntax by wrapping `actual` in an
-
# `ExpectationTarget`.
-
# @example
-
# expect(actual).to eq(expected)
-
# expect(actual).not_to eq(expected)
-
# @return [ExpectationTarget]
-
# @see ExpectationTarget#to
-
# @see ExpectationTarget#not_to
-
-
# Defines a matcher alias. The returned matcher's `description` will be overriden
-
# to reflect the phrasing of the new name, which will be used in failure messages
-
# when passed as an argument to another matcher in a composed matcher expression.
-
#
-
# @param new_name [Symbol] the new name for the matcher
-
# @param old_name [Symbol] the original name for the matcher
-
# @param options [Hash] options for the aliased matcher
-
# @option options [Class] :klass the ruby class to use as the decorator. (Not normally used).
-
# @yield [String] optional block that, when given, is used to define the overriden
-
# logic. The yielded arg is the original description or failure message. If no
-
# block is provided, a default override is used based on the old and new names.
-
#
-
# @example
-
# RSpec::Matchers.alias_matcher :a_list_that_sums_to, :sum_to
-
# sum_to(3).description # => "sum to 3"
-
# a_list_that_sums_to(3).description # => "a list that sums to 3"
-
#
-
# @example
-
# RSpec::Matchers.alias_matcher :a_list_sorted_by, :be_sorted_by do |description|
-
# description.sub("be sorted by", "a list sorted by")
-
# end
-
#
-
# be_sorted_by(:age).description # => "be sorted by age"
-
# a_list_sorted_by(:age).description # => "a list sorted by age"
-
#
-
# @!macro [attach] alias_matcher
-
# @!parse
-
# alias $1 $2
-
1
def self.alias_matcher(new_name, old_name, options={}, &description_override)
-
description_override ||= lambda do |old_desc|
-
old_desc.gsub(EnglishPhrasing.split_words(old_name), EnglishPhrasing.split_words(new_name))
-
57
end
-
113
klass = options.fetch(:klass) { AliasedMatcher }
-
-
57
define_method(new_name) do |*args, &block|
-
matcher = __send__(old_name, *args, &block)
-
klass.new(matcher, description_override)
-
end
-
end
-
-
# Defines a negated matcher. The returned matcher's `description` and `failure_message`
-
# will be overriden to reflect the phrasing of the new name, and the match logic will
-
# be based on the original matcher but negated.
-
#
-
# @param negated_name [Symbol] the name for the negated matcher
-
# @param base_name [Symbol] the name of the original matcher that will be negated
-
# @yield [String] optional block that, when given, is used to define the overriden
-
# logic. The yielded arg is the original description or failure message. If no
-
# block is provided, a default override is used based on the old and new names.
-
#
-
# @example
-
# RSpec::Matchers.define_negated_matcher :exclude, :include
-
# include(1, 2).description # => "include 1 and 2"
-
# exclude(1, 2).description # => "exclude 1 and 2"
-
#
-
# @note While the most obvious negated form may be to add a `not_` prefix,
-
# the failure messages you get with that form can be confusing (e.g.
-
# "expected [actual] to not [verb], but did not"). We've found it works
-
# best to find a more positive name for the negated form, such as
-
# `avoid_changing` rather than `not_change`.
-
1
def self.define_negated_matcher(negated_name, base_name, &description_override)
-
alias_matcher(negated_name, base_name, :klass => AliasedNegatedMatcher, &description_override)
-
end
-
-
# Allows multiple expectations in the provided block to fail, and then
-
# aggregates them into a single exception, rather than aborting on the
-
# first expectation failure like normal. This allows you to see all
-
# failures from an entire set of expectations without splitting each
-
# off into its own example (which may slow things down if the example
-
# setup is expensive).
-
#
-
# @param label [String] label for this aggregation block, which will be
-
# included in the aggregated exception message.
-
# @param metadata [Hash] additional metadata about this failure aggregation
-
# block. If multiple expectations fail, it will be exposed from the
-
# {Expectations::MultipleExpectationsNotMetError} exception. Mostly
-
# intended for internal RSpec use but you can use it as well.
-
# @yield Block containing as many expectation as you want. The block is
-
# simply yielded to, so you can trust that anything that works outside
-
# the block should work within it.
-
# @raise [Expectations::MultipleExpectationsNotMetError] raised when
-
# multiple expectations fail.
-
# @raise [Expectations::ExpectationNotMetError] raised when a single
-
# expectation fails.
-
# @raise [Exception] other sorts of exceptions will be raised as normal.
-
#
-
# @example
-
# aggregate_failures("verifying response") do
-
# expect(response.status).to eq(200)
-
# expect(response.headers).to include("Content-Type" => "text/plain")
-
# expect(response.body).to include("Success")
-
# end
-
#
-
# @note The implementation of this feature uses a thread-local variable,
-
# which means that if you have an expectation failure in another thread,
-
# it'll abort like normal.
-
1
def aggregate_failures(label=nil, metadata={}, &block)
-
Expectations::FailureAggregator.new(label, metadata).aggregate(&block)
-
end
-
-
# Passes if actual is truthy (anything but false or nil)
-
1
def be_truthy
-
BuiltIn::BeTruthy.new
-
end
-
1
alias_matcher :a_truthy_value, :be_truthy
-
-
# Passes if actual is falsey (false or nil)
-
1
def be_falsey
-
BuiltIn::BeFalsey.new
-
end
-
1
alias_matcher :be_falsy, :be_falsey
-
1
alias_matcher :a_falsey_value, :be_falsey
-
1
alias_matcher :a_falsy_value, :be_falsey
-
-
# Passes if actual is nil
-
1
def be_nil
-
BuiltIn::BeNil.new
-
end
-
1
alias_matcher :a_nil_value, :be_nil
-
-
# @example
-
# expect(actual).to be_truthy
-
# expect(actual).to be_falsey
-
# expect(actual).to be_nil
-
# expect(actual).to be_[arbitrary_predicate](*args)
-
# expect(actual).not_to be_nil
-
# expect(actual).not_to be_[arbitrary_predicate](*args)
-
#
-
# Given true, false, or nil, will pass if actual value is true, false or
-
# nil (respectively). Given no args means the caller should satisfy an if
-
# condition (to be or not to be).
-
#
-
# Predicates are any Ruby method that ends in a "?" and returns true or
-
# false. Given be_ followed by arbitrary_predicate (without the "?"),
-
# RSpec will match convert that into a query against the target object.
-
#
-
# The arbitrary_predicate feature will handle any predicate prefixed with
-
# "be_an_" (e.g. be_an_instance_of), "be_a_" (e.g. be_a_kind_of) or "be_"
-
# (e.g. be_empty), letting you choose the prefix that best suits the
-
# predicate.
-
1
def be(*args)
-
args.empty? ? Matchers::BuiltIn::Be.new : equal(*args)
-
end
-
1
alias_matcher :a_value, :be, :klass => AliasedMatcherWithOperatorSupport
-
-
# passes if target.kind_of?(klass)
-
1
def be_a(klass)
-
be_a_kind_of(klass)
-
end
-
1
alias_method :be_an, :be_a
-
-
# Passes if actual.instance_of?(expected)
-
#
-
# @example
-
# expect(5).to be_an_instance_of(Fixnum)
-
# expect(5).not_to be_an_instance_of(Numeric)
-
# expect(5).not_to be_an_instance_of(Float)
-
1
def be_an_instance_of(expected)
-
2
BuiltIn::BeAnInstanceOf.new(expected)
-
end
-
1
alias_method :be_instance_of, :be_an_instance_of
-
1
alias_matcher :an_instance_of, :be_an_instance_of
-
-
# Passes if actual.kind_of?(expected)
-
#
-
# @example
-
# expect(5).to be_a_kind_of(Fixnum)
-
# expect(5).to be_a_kind_of(Numeric)
-
# expect(5).not_to be_a_kind_of(Float)
-
1
def be_a_kind_of(expected)
-
BuiltIn::BeAKindOf.new(expected)
-
end
-
1
alias_method :be_kind_of, :be_a_kind_of
-
1
alias_matcher :a_kind_of, :be_a_kind_of
-
-
# Passes if actual.between?(min, max). Works with any Comparable object,
-
# including String, Symbol, Time, or Numeric (Fixnum, Bignum, Integer,
-
# Float, Complex, and Rational).
-
#
-
# By default, `be_between` is inclusive (i.e. passes when given either the max or min value),
-
# but you can make it `exclusive` by chaining that off the matcher.
-
#
-
# @example
-
# expect(5).to be_between(1, 10)
-
# expect(11).not_to be_between(1, 10)
-
# expect(10).not_to be_between(1, 10).exclusive
-
1
def be_between(min, max)
-
BuiltIn::BeBetween.new(min, max)
-
end
-
1
alias_matcher :a_value_between, :be_between
-
-
# Passes if actual == expected +/- delta
-
#
-
# @example
-
# expect(result).to be_within(0.5).of(3.0)
-
# expect(result).not_to be_within(0.5).of(3.0)
-
1
def be_within(delta)
-
BuiltIn::BeWithin.new(delta)
-
end
-
1
alias_matcher :a_value_within, :be_within
-
1
alias_matcher :within, :be_within
-
-
# Applied to a proc, specifies that its execution will cause some value to
-
# change.
-
#
-
# @param [Object] receiver
-
# @param [Symbol] message the message to send the receiver
-
#
-
# You can either pass <tt>receiver</tt> and <tt>message</tt>, or a block,
-
# but not both.
-
#
-
# When passing a block, it must use the `{ ... }` format, not
-
# do/end, as `{ ... }` binds to the `change` method, whereas do/end
-
# would errantly bind to the `expect(..).to` or `expect(...).not_to` method.
-
#
-
# You can chain any of the following off of the end to specify details
-
# about the change:
-
#
-
# * `from`
-
# * `to`
-
#
-
# or any one of:
-
#
-
# * `by`
-
# * `by_at_least`
-
# * `by_at_most`
-
#
-
# @example
-
# expect {
-
# team.add_player(player)
-
# }.to change(roster, :count)
-
#
-
# expect {
-
# team.add_player(player)
-
# }.to change(roster, :count).by(1)
-
#
-
# expect {
-
# team.add_player(player)
-
# }.to change(roster, :count).by_at_least(1)
-
#
-
# expect {
-
# team.add_player(player)
-
# }.to change(roster, :count).by_at_most(1)
-
#
-
# string = "string"
-
# expect {
-
# string.reverse!
-
# }.to change { string }.from("string").to("gnirts")
-
#
-
# string = "string"
-
# expect {
-
# string
-
# }.not_to change { string }.from("string")
-
#
-
# expect {
-
# person.happy_birthday
-
# }.to change(person, :birthday).from(32).to(33)
-
#
-
# expect {
-
# employee.develop_great_new_social_networking_app
-
# }.to change(employee, :title).from("Mail Clerk").to("CEO")
-
#
-
# expect {
-
# doctor.leave_office
-
# }.to change(doctor, :sign).from(/is in/).to(/is out/)
-
#
-
# user = User.new(:type => "admin")
-
# expect {
-
# user.symbolize_type
-
# }.to change(user, :type).from(String).to(Symbol)
-
#
-
# == Notes
-
#
-
# Evaluates `receiver.message` or `block` before and after it
-
# evaluates the block passed to `expect`.
-
#
-
# `expect( ... ).not_to change` supports the form that specifies `from`
-
# (which specifies what you expect the starting, unchanged value to be)
-
# but does not support forms with subsequent calls to `by`, `by_at_least`,
-
# `by_at_most` or `to`.
-
1
def change(receiver=nil, message=nil, &block)
-
BuiltIn::Change.new(receiver, message, &block)
-
end
-
1
alias_matcher :a_block_changing, :change
-
1
alias_matcher :changing, :change
-
-
# Passes if actual contains all of the expected regardless of order.
-
# This works for collections. Pass in multiple args and it will only
-
# pass if all args are found in collection.
-
#
-
# @note This is also available using the `=~` operator with `should`,
-
# but `=~` is not supported with `expect`.
-
#
-
# @example
-
# expect([1, 2, 3]).to contain_exactly(1, 2, 3)
-
# expect([1, 2, 3]).to contain_exactly(1, 3, 2)
-
#
-
# @see #match_array
-
1
def contain_exactly(*items)
-
BuiltIn::ContainExactly.new(items)
-
end
-
1
alias_matcher :a_collection_containing_exactly, :contain_exactly
-
1
alias_matcher :containing_exactly, :contain_exactly
-
-
# Passes if actual covers expected. This works for
-
# Ranges. You can also pass in multiple args
-
# and it will only pass if all args are found in Range.
-
#
-
# @example
-
# expect(1..10).to cover(5)
-
# expect(1..10).to cover(4, 6)
-
# expect(1..10).to cover(4, 6, 11) # fails
-
# expect(1..10).not_to cover(11)
-
# expect(1..10).not_to cover(5) # fails
-
#
-
# ### Warning:: Ruby >= 1.9 only
-
1
def cover(*values)
-
BuiltIn::Cover.new(*values)
-
end
-
1
alias_matcher :a_range_covering, :cover
-
1
alias_matcher :covering, :cover
-
-
# Matches if the actual value ends with the expected value(s). In the case
-
# of a string, matches against the last `expected.length` characters of the
-
# actual string. In the case of an array, matches against the last
-
# `expected.length` elements of the actual array.
-
#
-
# @example
-
# expect("this string").to end_with "string"
-
# expect([0, 1, 2, 3, 4]).to end_with 4
-
# expect([0, 2, 3, 4, 4]).to end_with 3, 4
-
1
def end_with(*expected)
-
BuiltIn::EndWith.new(*expected)
-
end
-
1
alias_matcher :a_collection_ending_with, :end_with
-
1
alias_matcher :a_string_ending_with, :end_with
-
1
alias_matcher :ending_with, :end_with
-
-
# Passes if <tt>actual == expected</tt>.
-
#
-
# See http://www.ruby-doc.org/core/classes/Object.html#M001057 for more
-
# information about equality in Ruby.
-
#
-
# @example
-
# expect(5).to eq(5)
-
# expect(5).not_to eq(3)
-
1
def eq(expected)
-
BuiltIn::Eq.new(expected)
-
end
-
1
alias_matcher :an_object_eq_to, :eq
-
1
alias_matcher :eq_to, :eq
-
-
# Passes if `actual.eql?(expected)`
-
#
-
# See http://www.ruby-doc.org/core/classes/Object.html#M001057 for more
-
# information about equality in Ruby.
-
#
-
# @example
-
# expect(5).to eql(5)
-
# expect(5).not_to eql(3)
-
1
def eql(expected)
-
BuiltIn::Eql.new(expected)
-
end
-
1
alias_matcher :an_object_eql_to, :eql
-
1
alias_matcher :eql_to, :eql
-
-
# Passes if <tt>actual.equal?(expected)</tt> (object identity).
-
#
-
# See http://www.ruby-doc.org/core/classes/Object.html#M001057 for more
-
# information about equality in Ruby.
-
#
-
# @example
-
# expect(5).to equal(5) # Fixnums are equal
-
# expect("5").not_to equal("5") # Strings that look the same are not the same object
-
1
def equal(expected)
-
BuiltIn::Equal.new(expected)
-
end
-
1
alias_matcher :an_object_equal_to, :equal
-
1
alias_matcher :equal_to, :equal
-
-
# Passes if `actual.exist?` or `actual.exists?`
-
#
-
# @example
-
# expect(File).to exist("path/to/file")
-
1
def exist(*args)
-
BuiltIn::Exist.new(*args)
-
end
-
1
alias_matcher :an_object_existing, :exist
-
1
alias_matcher :existing, :exist
-
-
# Passes if actual's attribute values match the expected attributes hash.
-
# This works no matter how you define your attribute readers.
-
#
-
# @example
-
# Person = Struct.new(:name, :age)
-
# person = Person.new("Bob", 32)
-
#
-
# expect(person).to have_attributes(:name => "Bob", :age => 32)
-
# expect(person).to have_attributes(:name => a_string_starting_with("B"), :age => (a_value > 30) )
-
#
-
# @note It will fail if actual doesn't respond to any of the expected attributes.
-
#
-
# @example
-
# expect(person).to have_attributes(:color => "red")
-
1
def have_attributes(expected)
-
BuiltIn::HaveAttributes.new(expected)
-
end
-
1
alias_matcher :an_object_having_attributes, :have_attributes
-
-
# Passes if actual includes expected. This works for
-
# collections and Strings. You can also pass in multiple args
-
# and it will only pass if all args are found in collection.
-
#
-
# @example
-
# expect([1,2,3]).to include(3)
-
# expect([1,2,3]).to include(2,3)
-
# expect([1,2,3]).to include(2,3,4) # fails
-
# expect([1,2,3]).not_to include(4)
-
# expect("spread").to include("read")
-
# expect("spread").not_to include("red")
-
# expect(:a => 1, :b => 2).to include(:a)
-
# expect(:a => 1, :b => 2).to include(:a, :b)
-
# expect(:a => 1, :b => 2).to include(:a => 1)
-
# expect(:a => 1, :b => 2).to include(:b => 2, :a => 1)
-
# expect(:a => 1, :b => 2).to include(:c) # fails
-
# expect(:a => 1, :b => 2).not_to include(:a => 2)
-
1
def include(*expected)
-
BuiltIn::Include.new(*expected)
-
end
-
1
alias_matcher :a_collection_including, :include
-
1
alias_matcher :a_string_including, :include
-
1
alias_matcher :a_hash_including, :include
-
1
alias_matcher :including, :include
-
-
# Passes if the provided matcher passes when checked against all
-
# elements of the collection.
-
#
-
# @example
-
# expect([1, 3, 5]).to all be_odd
-
# expect([1, 3, 6]).to all be_odd # fails
-
#
-
# @note The negative form `not_to all` is not supported. Instead
-
# use `not_to include` or pass a negative form of a matcher
-
# as the argument (e.g. `all exclude(:foo)`).
-
#
-
# @note You can also use this with compound matchers as well.
-
#
-
# @example
-
# expect([1, 3, 5]).to all( be_odd.and be_an(Integer) )
-
1
def all(expected)
-
BuiltIn::All.new(expected)
-
end
-
-
# Given a `Regexp` or `String`, passes if `actual.match(pattern)`
-
# Given an arbitrary nested data structure (e.g. arrays and hashes),
-
# matches if `expected === actual` || `actual == expected` for each
-
# pair of elements.
-
#
-
# @example
-
# expect(email).to match(/^([^\s]+)((?:[-a-z0-9]+\.)+[a-z]{2,})$/i)
-
# expect(email).to match("@example.com")
-
#
-
# @example
-
# hash = {
-
# :a => {
-
# :b => ["foo", 5],
-
# :c => { :d => 2.05 }
-
# }
-
# }
-
#
-
# expect(hash).to match(
-
# :a => {
-
# :b => a_collection_containing_exactly(
-
# a_string_starting_with("f"),
-
# an_instance_of(Fixnum)
-
# ),
-
# :c => { :d => (a_value < 3) }
-
# }
-
# )
-
#
-
# @note The `match_regex` alias is deprecated and is not recommended for use.
-
# It was added in 2.12.1 to facilitate its use from within custom
-
# matchers (due to how the custom matcher DSL was evaluated in 2.x,
-
# `match` could not be used there), but is no longer needed in 3.x.
-
1
def match(expected)
-
4
BuiltIn::Match.new(expected)
-
end
-
1
alias_matcher :match_regex, :match
-
1
alias_matcher :an_object_matching, :match
-
1
alias_matcher :a_string_matching, :match
-
1
alias_matcher :matching, :match
-
-
# An alternate form of `contain_exactly` that accepts
-
# the expected contents as a single array arg rather
-
# that splatted out as individual items.
-
#
-
# @example
-
# expect(results).to contain_exactly(1, 2)
-
# # is identical to:
-
# expect(results).to match_array([1, 2])
-
#
-
# @see #contain_exactly
-
1
def match_array(items)
-
contain_exactly(*items)
-
end
-
-
# With no arg, passes if the block outputs `to_stdout` or `to_stderr`.
-
# With a string, passes if the block outputs that specific string `to_stdout` or `to_stderr`.
-
# With a regexp or matcher, passes if the block outputs a string `to_stdout` or `to_stderr` that matches.
-
#
-
# To capture output from any spawned subprocess as well, use `to_stdout_from_any_process` or
-
# `to_stderr_from_any_process`. Output from any process that inherits the main process's corresponding
-
# standard stream will be captured.
-
#
-
# @example
-
# expect { print 'foo' }.to output.to_stdout
-
# expect { print 'foo' }.to output('foo').to_stdout
-
# expect { print 'foo' }.to output(/foo/).to_stdout
-
#
-
# expect { do_something }.to_not output.to_stdout
-
#
-
# expect { warn('foo') }.to output.to_stderr
-
# expect { warn('foo') }.to output('foo').to_stderr
-
# expect { warn('foo') }.to output(/foo/).to_stderr
-
#
-
# expect { do_something }.to_not output.to_stderr
-
#
-
# expect { system('echo foo') }.to output("foo\n").to_stdout_from_any_process
-
# expect { system('echo foo', out: :err) }.to output("foo\n").to_stderr_from_any_process
-
#
-
# @note `to_stdout` and `to_stderr` work by temporarily replacing `$stdout` or `$stderr`,
-
# so they're not able to intercept stream output that explicitly uses `STDOUT`/`STDERR`
-
# or that uses a reference to `$stdout`/`$stderr` that was stored before the
-
# matcher was used.
-
# @note `to_stdout_from_any_process` and `to_stderr_from_any_process` use Tempfiles, and
-
# are thus significantly (~30x) slower than `to_stdout` and `to_stderr`.
-
1
def output(expected=nil)
-
BuiltIn::Output.new(expected)
-
end
-
1
alias_matcher :a_block_outputting, :output
-
-
# With no args, matches if any error is raised.
-
# With a named error, matches only if that specific error is raised.
-
# With a named error and messsage specified as a String, matches only if both match.
-
# With a named error and messsage specified as a Regexp, matches only if both match.
-
# Pass an optional block to perform extra verifications on the exception matched
-
#
-
# @example
-
# expect { do_something_risky }.to raise_error
-
# expect { do_something_risky }.to raise_error(PoorRiskDecisionError)
-
# expect { do_something_risky }.to raise_error(PoorRiskDecisionError) { |error| expect(error.data).to eq 42 }
-
# expect { do_something_risky }.to raise_error(PoorRiskDecisionError, "that was too risky")
-
# expect { do_something_risky }.to raise_error(PoorRiskDecisionError, /oo ri/)
-
#
-
# expect { do_something_risky }.not_to raise_error
-
1
def raise_error(error=nil, message=nil, &block)
-
BuiltIn::RaiseError.new(error, message, &block)
-
end
-
1
alias_method :raise_exception, :raise_error
-
-
1
alias_matcher :a_block_raising, :raise_error do |desc|
-
desc.sub("raise", "a block raising")
-
end
-
-
1
alias_matcher :raising, :raise_error do |desc|
-
desc.sub("raise", "raising")
-
end
-
-
# Matches if the target object responds to all of the names
-
# provided. Names can be Strings or Symbols.
-
#
-
# @example
-
# expect("string").to respond_to(:length)
-
#
-
1
def respond_to(*names)
-
BuiltIn::RespondTo.new(*names)
-
end
-
1
alias_matcher :an_object_responding_to, :respond_to
-
1
alias_matcher :responding_to, :respond_to
-
-
# Passes if the submitted block returns true. Yields target to the
-
# block.
-
#
-
# Generally speaking, this should be thought of as a last resort when
-
# you can't find any other way to specify the behaviour you wish to
-
# specify.
-
#
-
# If you do find yourself in such a situation, you could always write
-
# a custom matcher, which would likely make your specs more expressive.
-
#
-
# @param description [String] optional description to be used for this matcher.
-
#
-
# @example
-
# expect(5).to satisfy { |n| n > 3 }
-
# expect(5).to satisfy("be greater than 3") { |n| n > 3 }
-
1
def satisfy(description="satisfy block", &block)
-
BuiltIn::Satisfy.new(description, &block)
-
end
-
1
alias_matcher :an_object_satisfying, :satisfy
-
1
alias_matcher :satisfying, :satisfy
-
-
# Matches if the actual value starts with the expected value(s). In the
-
# case of a string, matches against the first `expected.length` characters
-
# of the actual string. In the case of an array, matches against the first
-
# `expected.length` elements of the actual array.
-
#
-
# @example
-
# expect("this string").to start_with "this s"
-
# expect([0, 1, 2, 3, 4]).to start_with 0
-
# expect([0, 2, 3, 4, 4]).to start_with 0, 1
-
1
def start_with(*expected)
-
BuiltIn::StartWith.new(*expected)
-
end
-
1
alias_matcher :a_collection_starting_with, :start_with
-
1
alias_matcher :a_string_starting_with, :start_with
-
1
alias_matcher :starting_with, :start_with
-
-
# Given no argument, matches if a proc throws any Symbol.
-
#
-
# Given a Symbol, matches if the given proc throws the specified Symbol.
-
#
-
# Given a Symbol and an arg, matches if the given proc throws the
-
# specified Symbol with the specified arg.
-
#
-
# @example
-
# expect { do_something_risky }.to throw_symbol
-
# expect { do_something_risky }.to throw_symbol(:that_was_risky)
-
# expect { do_something_risky }.to throw_symbol(:that_was_risky, 'culprit')
-
#
-
# expect { do_something_risky }.not_to throw_symbol
-
# expect { do_something_risky }.not_to throw_symbol(:that_was_risky)
-
# expect { do_something_risky }.not_to throw_symbol(:that_was_risky, 'culprit')
-
1
def throw_symbol(expected_symbol=nil, expected_arg=nil)
-
BuiltIn::ThrowSymbol.new(expected_symbol, expected_arg)
-
end
-
-
1
alias_matcher :a_block_throwing, :throw_symbol do |desc|
-
desc.sub("throw", "a block throwing")
-
end
-
-
1
alias_matcher :throwing, :throw_symbol do |desc|
-
desc.sub("throw", "throwing")
-
end
-
-
# Passes if the method called in the expect block yields, regardless
-
# of whether or not arguments are yielded.
-
#
-
# @example
-
# expect { |b| 5.tap(&b) }.to yield_control
-
# expect { |b| "a".to_sym(&b) }.not_to yield_control
-
#
-
# @note Your expect block must accept a parameter and pass it on to
-
# the method-under-test as a block.
-
1
def yield_control
-
BuiltIn::YieldControl.new
-
end
-
1
alias_matcher :a_block_yielding_control, :yield_control
-
1
alias_matcher :yielding_control, :yield_control
-
-
# Passes if the method called in the expect block yields with
-
# no arguments. Fails if it does not yield, or yields with arguments.
-
#
-
# @example
-
# expect { |b| User.transaction(&b) }.to yield_with_no_args
-
# expect { |b| 5.tap(&b) }.not_to yield_with_no_args # because it yields with `5`
-
# expect { |b| "a".to_sym(&b) }.not_to yield_with_no_args # because it does not yield
-
#
-
# @note Your expect block must accept a parameter and pass it on to
-
# the method-under-test as a block.
-
# @note This matcher is not designed for use with methods that yield
-
# multiple times.
-
1
def yield_with_no_args
-
BuiltIn::YieldWithNoArgs.new
-
end
-
1
alias_matcher :a_block_yielding_with_no_args, :yield_with_no_args
-
1
alias_matcher :yielding_with_no_args, :yield_with_no_args
-
-
# Given no arguments, matches if the method called in the expect
-
# block yields with arguments (regardless of what they are or how
-
# many there are).
-
#
-
# Given arguments, matches if the method called in the expect block
-
# yields with arguments that match the given arguments.
-
#
-
# Argument matching is done using `===` (the case match operator)
-
# and `==`. If the expected and actual arguments match with either
-
# operator, the matcher will pass.
-
#
-
# @example
-
# expect { |b| 5.tap(&b) }.to yield_with_args # because #tap yields an arg
-
# expect { |b| 5.tap(&b) }.to yield_with_args(5) # because 5 == 5
-
# expect { |b| 5.tap(&b) }.to yield_with_args(Fixnum) # because Fixnum === 5
-
# expect { |b| File.open("f.txt", &b) }.to yield_with_args(/txt/) # because /txt/ === "f.txt"
-
#
-
# expect { |b| User.transaction(&b) }.not_to yield_with_args # because it yields no args
-
# expect { |b| 5.tap(&b) }.not_to yield_with_args(1, 2, 3)
-
#
-
# @note Your expect block must accept a parameter and pass it on to
-
# the method-under-test as a block.
-
# @note This matcher is not designed for use with methods that yield
-
# multiple times.
-
1
def yield_with_args(*args)
-
BuiltIn::YieldWithArgs.new(*args)
-
end
-
1
alias_matcher :a_block_yielding_with_args, :yield_with_args
-
1
alias_matcher :yielding_with_args, :yield_with_args
-
-
# Designed for use with methods that repeatedly yield (such as
-
# iterators). Passes if the method called in the expect block yields
-
# multiple times with arguments matching those given.
-
#
-
# Argument matching is done using `===` (the case match operator)
-
# and `==`. If the expected and actual arguments match with either
-
# operator, the matcher will pass.
-
#
-
# @example
-
# expect { |b| [1, 2, 3].each(&b) }.to yield_successive_args(1, 2, 3)
-
# expect { |b| { :a => 1, :b => 2 }.each(&b) }.to yield_successive_args([:a, 1], [:b, 2])
-
# expect { |b| [1, 2, 3].each(&b) }.not_to yield_successive_args(1, 2)
-
#
-
# @note Your expect block must accept a parameter and pass it on to
-
# the method-under-test as a block.
-
1
def yield_successive_args(*args)
-
BuiltIn::YieldSuccessiveArgs.new(*args)
-
end
-
1
alias_matcher :a_block_yielding_successive_args, :yield_successive_args
-
1
alias_matcher :yielding_successive_args, :yield_successive_args
-
-
# Delegates to {RSpec::Expectations.configuration}.
-
# This is here because rspec-core's `expect_with` option
-
# looks for a `configuration` method on the mixin
-
# (`RSpec::Matchers`) to yield to a block.
-
# @return [RSpec::Expectations::Configuration] the configuration object
-
1
def self.configuration
-
2
Expectations.configuration
-
end
-
-
1
private
-
-
1
BE_PREDICATE_REGEX = /^(be_(?:an?_)?)(.*)/
-
1
HAS_REGEX = /^(?:have_)(.*)/
-
1
DYNAMIC_MATCHER_REGEX = Regexp.union(BE_PREDICATE_REGEX, HAS_REGEX)
-
-
1
def method_missing(method, *args, &block)
-
1
case method.to_s
-
when BE_PREDICATE_REGEX
-
1
BuiltIn::BePredicate.new(method, *args, &block)
-
when HAS_REGEX
-
BuiltIn::Has.new(method, *args, &block)
-
else
-
super
-
end
-
end
-
-
1
if RUBY_VERSION.to_f >= 1.9
-
1
def respond_to_missing?(method, *)
-
method =~ DYNAMIC_MATCHER_REGEX || super
-
end
-
else # for 1.8.7
-
# :nocov:
-
skipped
def respond_to?(method, *)
-
skipped
method = method.to_s
-
skipped
method =~ DYNAMIC_MATCHER_REGEX || super
-
skipped
end
-
skipped
public :respond_to?
-
# :nocov:
-
end
-
-
# @api private
-
1
def self.is_a_matcher?(obj)
-
4
return true if ::RSpec::Matchers::BuiltIn::BaseMatcher === obj
-
4
begin
-
4
return false if obj.respond_to?(:i_respond_to_everything_so_im_not_really_a_matcher)
-
rescue NoMethodError
-
# Some objects, like BasicObject, don't implemented standard
-
# reflection methods.
-
return false
-
end
-
4
return false unless obj.respond_to?(:matches?)
-
-
obj.respond_to?(:failure_message) ||
-
obj.respond_to?(:failure_message_for_should) # support legacy matchers
-
end
-
-
1
::RSpec::Support.register_matcher_definition do |obj|
-
is_a_matcher?(obj)
-
end
-
-
# @api private
-
1
def self.is_a_describable_matcher?(obj)
-
is_a_matcher?(obj) && obj.respond_to?(:description)
-
end
-
-
1
if RSpec::Support::Ruby.mri? && RUBY_VERSION[0, 3] == '1.9'
-
# @api private
-
# Note that `included` doesn't work for this because it is triggered
-
# _after_ `RSpec::Matchers` is an ancestor of the inclusion host, rather
-
# than _before_, like `append_features`. It's important we check this before
-
# in order to find the cases where it was already previously included.
-
def self.append_features(mod)
-
return super if mod < self # `mod < self` indicates a re-inclusion.
-
-
subclasses = ObjectSpace.each_object(Class).select { |c| c < mod && c < self }
-
return super unless subclasses.any?
-
-
subclasses.reject! { |s| subclasses.any? { |s2| s < s2 } } # Filter to the root ancestor.
-
subclasses = subclasses.map { |s| "`#{s}`" }.join(", ")
-
-
RSpec.warning "`#{self}` has been included in a superclass (`#{mod}`) " \
-
"after previously being included in subclasses (#{subclasses}), " \
-
"which can trigger infinite recursion from `super` due to an MRI 1.9 bug " \
-
"(https://redmine.ruby-lang.org/issues/3351). To work around this, " \
-
"either upgrade to MRI 2.0+, include a dup of the module (e.g. " \
-
"`include #{self}.dup`), or find a way to include `#{self}` in `#{mod}` " \
-
"before it is included in subclasses (#{subclasses}). See " \
-
"https://github.com/rspec/rspec-expectations/issues/814 for more info"
-
-
super
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
# Decorator that wraps a matcher and overrides `description`
-
# using the provided block in order to support an alias
-
# of a matcher. This is intended for use when composing
-
# matchers, so that you can use an expression like
-
# `include( a_value_within(0.1).of(3) )` rather than
-
# `include( be_within(0.1).of(3) )`, and have the corresponding
-
# description read naturally.
-
#
-
# @api private
-
1
class AliasedMatcher < MatcherDelegator
-
1
def initialize(base_matcher, description_block)
-
@description_block = description_block
-
super(base_matcher)
-
end
-
-
# Forward messages on to the wrapped matcher.
-
# Since many matchers provide a fluent interface
-
# (e.g. `a_value_within(0.1).of(3)`), we need to wrap
-
# the returned value if it responds to `description`,
-
# so that our override can be applied when it is eventually
-
# used.
-
1
def method_missing(*)
-
return_val = super
-
return return_val unless RSpec::Matchers.is_a_matcher?(return_val)
-
self.class.new(return_val, @description_block)
-
end
-
-
# Provides the description of the aliased matcher. Aliased matchers
-
# are designed to behave identically to the original matcher except
-
# for the description and failure messages. The description is different
-
# to reflect the aliased name.
-
#
-
# @api private
-
1
def description
-
@description_block.call(super)
-
end
-
-
# Provides the failure_message of the aliased matcher. Aliased matchers
-
# are designed to behave identically to the original matcher except
-
# for the description and failure messages. The failure_message is different
-
# to reflect the aliased name.
-
#
-
# @api private
-
1
def failure_message
-
@description_block.call(super)
-
end
-
-
# Provides the failure_message_when_negated of the aliased matcher. Aliased matchers
-
# are designed to behave identically to the original matcher except
-
# for the description and failure messages. The failure_message_when_negated is different
-
# to reflect the aliased name.
-
#
-
# @api private
-
1
def failure_message_when_negated
-
@description_block.call(super)
-
end
-
end
-
-
# Decorator used for matchers that have special implementations of
-
# operators like `==` and `===`.
-
# @private
-
1
class AliasedMatcherWithOperatorSupport < AliasedMatcher
-
# We undef these so that they get delegated via `method_missing`.
-
1
undef ==
-
1
undef ===
-
end
-
-
# @private
-
1
class AliasedNegatedMatcher < AliasedMatcher
-
1
def matches?(*args, &block)
-
if @base_matcher.respond_to?(:does_not_match?)
-
@base_matcher.does_not_match?(*args, &block)
-
else
-
!super
-
end
-
end
-
-
1
def does_not_match?(*args, &block)
-
@base_matcher.matches?(*args, &block)
-
end
-
-
1
def failure_message
-
optimal_failure_message(__method__, :failure_message_when_negated)
-
end
-
-
1
def failure_message_when_negated
-
optimal_failure_message(__method__, :failure_message)
-
end
-
-
1
private
-
-
1
DefaultFailureMessages = BuiltIn::BaseMatcher::DefaultFailureMessages
-
-
# For a matcher that uses the default failure messages, we prefer to
-
# use the override provided by the `description_block`, because it
-
# includes the phrasing that the user has expressed a preference for
-
# by going through the effort of defining a negated matcher.
-
#
-
# However, if the override didn't actually change anything, then we
-
# should return the opposite failure message instead -- the overriden
-
# message is going to be confusing if we return it as-is, as it represents
-
# the non-negated failure message for a negated match (or vice versa).
-
1
def optimal_failure_message(same, inverted)
-
if DefaultFailureMessages.has_default_failure_messages?(@base_matcher)
-
base_message = @base_matcher.__send__(same)
-
overriden = @description_block.call(base_message)
-
return overriden if overriden != base_message
-
end
-
-
@base_matcher.__send__(inverted)
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_matchers "built_in/base_matcher"
-
-
1
module RSpec
-
1
module Matchers
-
# Container module for all built-in matchers. The matcher classes are here
-
# (rather than directly under `RSpec::Matchers`) in order to prevent name
-
# collisions, since `RSpec::Matchers` gets included into the user's namespace.
-
#
-
# Autoloading is used to delay when the matcher classes get loaded, allowing
-
# rspec-matchers to boot faster, and avoiding loading matchers the user is
-
# not using.
-
1
module BuiltIn
-
1
autoload :BeAKindOf, 'rspec/matchers/built_in/be_kind_of'
-
1
autoload :BeAnInstanceOf, 'rspec/matchers/built_in/be_instance_of'
-
1
autoload :BeBetween, 'rspec/matchers/built_in/be_between'
-
1
autoload :Be, 'rspec/matchers/built_in/be'
-
1
autoload :BeComparedTo, 'rspec/matchers/built_in/be'
-
1
autoload :BeFalsey, 'rspec/matchers/built_in/be'
-
1
autoload :BeNil, 'rspec/matchers/built_in/be'
-
1
autoload :BePredicate, 'rspec/matchers/built_in/be'
-
1
autoload :BeTruthy, 'rspec/matchers/built_in/be'
-
1
autoload :BeWithin, 'rspec/matchers/built_in/be_within'
-
1
autoload :Change, 'rspec/matchers/built_in/change'
-
1
autoload :Compound, 'rspec/matchers/built_in/compound'
-
1
autoload :ContainExactly, 'rspec/matchers/built_in/contain_exactly'
-
1
autoload :Cover, 'rspec/matchers/built_in/cover'
-
1
autoload :EndWith, 'rspec/matchers/built_in/start_or_end_with'
-
1
autoload :Eq, 'rspec/matchers/built_in/eq'
-
1
autoload :Eql, 'rspec/matchers/built_in/eql'
-
1
autoload :Equal, 'rspec/matchers/built_in/equal'
-
1
autoload :Exist, 'rspec/matchers/built_in/exist'
-
1
autoload :Has, 'rspec/matchers/built_in/has'
-
1
autoload :HaveAttributes, 'rspec/matchers/built_in/have_attributes'
-
1
autoload :Include, 'rspec/matchers/built_in/include'
-
1
autoload :All, 'rspec/matchers/built_in/all'
-
1
autoload :Match, 'rspec/matchers/built_in/match'
-
1
autoload :NegativeOperatorMatcher, 'rspec/matchers/built_in/operators'
-
1
autoload :OperatorMatcher, 'rspec/matchers/built_in/operators'
-
1
autoload :Output, 'rspec/matchers/built_in/output'
-
1
autoload :PositiveOperatorMatcher, 'rspec/matchers/built_in/operators'
-
1
autoload :RaiseError, 'rspec/matchers/built_in/raise_error'
-
1
autoload :RespondTo, 'rspec/matchers/built_in/respond_to'
-
1
autoload :Satisfy, 'rspec/matchers/built_in/satisfy'
-
1
autoload :StartWith, 'rspec/matchers/built_in/start_or_end_with'
-
1
autoload :ThrowSymbol, 'rspec/matchers/built_in/throw_symbol'
-
1
autoload :YieldControl, 'rspec/matchers/built_in/yield'
-
1
autoload :YieldSuccessiveArgs, 'rspec/matchers/built_in/yield'
-
1
autoload :YieldWithArgs, 'rspec/matchers/built_in/yield'
-
1
autoload :YieldWithNoArgs, 'rspec/matchers/built_in/yield'
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
#
-
# Used _internally_ as a base class for matchers that ship with
-
# rspec-expectations and rspec-rails.
-
#
-
# ### Warning:
-
#
-
# This class is for internal use, and subject to change without notice.
-
# We strongly recommend that you do not base your custom matchers on this
-
# class. If/when this changes, we will announce it and remove this warning.
-
1
class BaseMatcher
-
1
include RSpec::Matchers::Composable
-
-
# @api private
-
# Used to detect when no arg is passed to `initialize`.
-
# `nil` cannot be used because it's a valid value to pass.
-
1
UNDEFINED = Object.new.freeze
-
-
# @private
-
1
attr_reader :actual, :expected, :rescued_exception
-
-
1
def initialize(expected=UNDEFINED)
-
6
@expected = expected unless UNDEFINED.equal?(expected)
-
end
-
-
# @api private
-
# Indicates if the match is successful. Delegates to `match`, which
-
# should be defined on a subclass. Takes care of consistently
-
# initializing the `actual` attribute.
-
1
def matches?(actual)
-
6
@actual = actual
-
6
match(expected, actual)
-
end
-
-
# @api private
-
# Used to wrap a block of code that will indicate failure by
-
# raising one of the named exceptions.
-
#
-
# This is used by rspec-rails for some of its matchers that
-
# wrap rails' assertions.
-
1
def match_unless_raises(*exceptions)
-
exceptions.unshift Exception if exceptions.empty?
-
begin
-
yield
-
true
-
rescue *exceptions => @rescued_exception
-
false
-
end
-
end
-
-
# @api private
-
# Generates a description using {EnglishPhrasing}.
-
# @return [String]
-
1
def description
-
desc = EnglishPhrasing.split_words(self.class.matcher_name)
-
desc << EnglishPhrasing.list(@expected) if defined?(@expected)
-
desc
-
end
-
-
# @api private
-
# Matchers are not diffable by default. Override this to make your
-
# subclass diffable.
-
1
def diffable?
-
false
-
end
-
-
# @api private
-
# Most matchers are value matchers (i.e. meant to work with `expect(value)`)
-
# rather than block matchers (i.e. meant to work with `expect { }`), so
-
# this defaults to false. Block matchers must override this to return true.
-
1
def supports_block_expectations?
-
false
-
end
-
-
# @api private
-
1
def expects_call_stack_jump?
-
false
-
end
-
-
# @private
-
1
def expected_formatted
-
RSpec::Support::ObjectFormatter.format(@expected)
-
end
-
-
# @private
-
1
def actual_formatted
-
RSpec::Support::ObjectFormatter.format(@actual)
-
end
-
-
# @private
-
1
def self.matcher_name
-
@matcher_name ||= underscore(name.split('::').last)
-
end
-
-
# @private
-
# Borrowed from ActiveSupport.
-
1
def self.underscore(camel_cased_word)
-
word = camel_cased_word.to_s.dup
-
word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
-
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
-
word.tr!('-', '_')
-
word.downcase!
-
word
-
end
-
1
private_class_method :underscore
-
-
1
private
-
-
1
def assert_ivars(*expected_ivars)
-
return unless (expected_ivars - present_ivars).any?
-
ivar_list = EnglishPhrasing.list(expected_ivars)
-
raise "#{self.class.name} needs to supply#{ivar_list}"
-
end
-
-
1
if RUBY_VERSION.to_f < 1.9
-
# :nocov:
-
skipped
def present_ivars
-
skipped
instance_variables.map(&:to_sym)
-
skipped
end
-
# :nocov:
-
else
-
1
alias present_ivars instance_variables
-
end
-
-
# @private
-
1
module HashFormatting
-
# `{ :a => 5, :b => 2 }.inspect` produces:
-
#
-
# {:a=>5, :b=>2}
-
#
-
# ...but it looks much better as:
-
#
-
# {:a => 5, :b => 2}
-
#
-
# This is idempotent and safe to run on a string multiple times.
-
1
def improve_hash_formatting(inspect_string)
-
inspect_string.gsub(/(\S)=>(\S)/, '\1 => \2')
-
end
-
1
module_function :improve_hash_formatting
-
end
-
-
1
include HashFormatting
-
-
# @api private
-
# Provides default implementations of failure messages, based on the `description`.
-
1
module DefaultFailureMessages
-
# @api private
-
# Provides a good generic failure message. Based on `description`.
-
# When subclassing, if you are not satisfied with this failure message
-
# you often only need to override `description`.
-
# @return [String]
-
1
def failure_message
-
"expected #{description_of @actual} to #{description}"
-
end
-
-
# @api private
-
# Provides a good generic negative failure message. Based on `description`.
-
# When subclassing, if you are not satisfied with this failure message
-
# you often only need to override `description`.
-
# @return [String]
-
1
def failure_message_when_negated
-
"expected #{description_of @actual} not to #{description}"
-
end
-
-
# @private
-
1
def self.has_default_failure_messages?(matcher)
-
matcher.method(:failure_message).owner == self &&
-
matcher.method(:failure_message_when_negated).owner == self
-
rescue NameError
-
false
-
end
-
end
-
-
1
include DefaultFailureMessages
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
# Provides the implementation for `be_truthy`.
-
# Not intended to be instantiated directly.
-
1
class BeTruthy < BaseMatcher
-
# @api private
-
# @return [String]
-
1
def failure_message
-
"expected: truthy value\n got: #{actual_formatted}"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
"expected: falsey value\n got: #{actual_formatted}"
-
end
-
-
1
private
-
-
1
def match(_, actual)
-
!!actual
-
end
-
end
-
-
# @api private
-
# Provides the implementation for `be_falsey`.
-
# Not intended to be instantiated directly.
-
1
class BeFalsey < BaseMatcher
-
# @api private
-
# @return [String]
-
1
def failure_message
-
"expected: falsey value\n got: #{actual_formatted}"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
"expected: truthy value\n got: #{actual_formatted}"
-
end
-
-
1
private
-
-
1
def match(_, actual)
-
!actual
-
end
-
end
-
-
# @api private
-
# Provides the implementation for `be_nil`.
-
# Not intended to be instantiated directly.
-
1
class BeNil < BaseMatcher
-
# @api private
-
# @return [String]
-
1
def failure_message
-
"expected: nil\n got: #{actual_formatted}"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
"expected: not nil\n got: nil"
-
end
-
-
1
private
-
-
1
def match(_, actual)
-
actual.nil?
-
end
-
end
-
-
# @private
-
1
module BeHelpers
-
1
private
-
-
1
def args_to_s
-
@args.empty? ? "" : parenthesize(inspected_args.join(', '))
-
end
-
-
1
def parenthesize(string)
-
"(#{string})"
-
end
-
-
1
def inspected_args
-
@args.map { |a| RSpec::Support::ObjectFormatter.format(a) }
-
end
-
-
1
def expected_to_sentence
-
EnglishPhrasing.split_words(@expected)
-
end
-
-
1
def args_to_sentence
-
EnglishPhrasing.list(@args)
-
end
-
end
-
-
# @api private
-
# Provides the implementation for `be`.
-
# Not intended to be instantiated directly.
-
1
class Be < BaseMatcher
-
1
include BeHelpers
-
-
1
def initialize(*args)
-
@args = args
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message
-
"expected #{actual_formatted} to evaluate to true"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
"expected #{actual_formatted} to evaluate to false"
-
end
-
-
1
[:==, :<, :<=, :>=, :>, :===, :=~].each do |operator|
-
7
define_method operator do |operand|
-
BeComparedTo.new(operand, operator)
-
end
-
end
-
-
1
private
-
-
1
def match(_, actual)
-
!!actual
-
end
-
end
-
-
# @api private
-
# Provides the implementation of `be <operator> value`.
-
# Not intended to be instantiated directly.
-
1
class BeComparedTo < BaseMatcher
-
1
include BeHelpers
-
-
1
def initialize(operand, operator)
-
@expected, @operator = operand, operator
-
@args = []
-
end
-
-
1
def matches?(actual)
-
@actual = actual
-
@actual.__send__ @operator, @expected
-
rescue ArgumentError
-
false
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message
-
"expected: #{@operator} #{expected_formatted}\n got: #{@operator.to_s.gsub(/./, ' ')} #{actual_formatted}"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
message = "`expect(#{actual_formatted}).not_to be #{@operator} #{expected_formatted}`"
-
if [:<, :>, :<=, :>=].include?(@operator)
-
message + " not only FAILED, it is a bit confusing."
-
else
-
message
-
end
-
end
-
-
# @api private
-
# @return [String]
-
1
def description
-
"be #{@operator} #{expected_to_sentence}#{args_to_sentence}"
-
end
-
end
-
-
# @api private
-
# Provides the implementation of `be_<predicate>`.
-
# Not intended to be instantiated directly.
-
1
class BePredicate < BaseMatcher
-
1
include BeHelpers
-
-
1
def initialize(*args, &block)
-
1
@expected = parse_expected(args.shift)
-
1
@args = args
-
1
@block = block
-
end
-
-
1
def matches?(actual, &block)
-
1
@actual = actual
-
1
@block ||= block
-
1
predicate_accessible? && predicate_matches?
-
end
-
-
1
def does_not_match?(actual, &block)
-
@actual = actual
-
@block ||= block
-
predicate_accessible? && !predicate_matches?
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message
-
failure_message_expecting(true)
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
failure_message_expecting(false)
-
end
-
-
# @api private
-
# @return [String]
-
1
def description
-
"#{prefix_to_sentence}#{expected_to_sentence}#{args_to_sentence}"
-
end
-
-
1
private
-
-
1
def predicate_accessible?
-
1
actual.respond_to?(predicate) || actual.respond_to?(present_tense_predicate)
-
end
-
-
# support 1.8.7, evaluate once at load time for performance
-
1
if String === methods.first
-
# :nocov:
-
skipped
def private_predicate?
-
skipped
@actual.private_methods.include? predicate.to_s
-
skipped
end
-
# :nocov:
-
else
-
1
def private_predicate?
-
@actual.private_methods.include? predicate
-
end
-
end
-
-
1
def predicate_matches?
-
1
method_name = actual.respond_to?(predicate) ? predicate : present_tense_predicate
-
1
@predicate_matches = actual.__send__(method_name, *@args, &@block)
-
end
-
-
1
def predicate
-
3
:"#{@expected}?"
-
end
-
-
1
def present_tense_predicate
-
:"#{@expected}s?"
-
end
-
-
1
def parse_expected(expected)
-
1
@prefix, expected = prefix_and_expected(expected)
-
1
expected
-
end
-
-
1
def prefix_and_expected(symbol)
-
1
Matchers::BE_PREDICATE_REGEX.match(symbol.to_s).captures.compact
-
end
-
-
1
def prefix_to_sentence
-
EnglishPhrasing.split_words(@prefix)
-
end
-
-
1
def failure_message_expecting(value)
-
validity_message ||
-
"expected `#{actual_formatted}.#{predicate}#{args_to_s}` to return #{value}, got #{description_of @predicate_matches}"
-
end
-
-
1
def validity_message
-
return nil if predicate_accessible?
-
-
msg = "expected #{actual_formatted} to respond to `#{predicate}`"
-
-
if private_predicate?
-
msg << " but `#{predicate}` is a private method"
-
elsif predicate == :true?
-
msg << " or perhaps you meant `be true` or `be_truthy`"
-
elsif predicate == :false?
-
msg << " or perhaps you meant `be false` or `be_falsey`"
-
end
-
-
msg
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
# Provides the implementation for `be_an_instance_of`.
-
# Not intended to be instantiated directly.
-
1
class BeAnInstanceOf < BaseMatcher
-
# @api private
-
# @return [String]
-
1
def description
-
"be an instance of #{expected}"
-
end
-
-
1
private
-
-
1
def match(expected, actual)
-
2
actual.instance_of? expected
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
# Provides the implementation for `contain_exactly` and `match_array`.
-
# Not intended to be instantiated directly.
-
1
class ContainExactly < BaseMatcher
-
# @api private
-
# @return [String]
-
1
def failure_message
-
if Array === actual
-
generate_failure_message
-
else
-
"expected a collection that can be converted to an array with " \
-
"`#to_ary` or `#to_a`, but got #{actual_formatted}"
-
end
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
list = EnglishPhrasing.list(surface_descriptions_in(expected))
-
"expected #{actual_formatted} not to contain exactly#{list}"
-
end
-
-
# @api private
-
# @return [String]
-
1
def description
-
list = EnglishPhrasing.list(surface_descriptions_in(expected))
-
"contain exactly#{list}"
-
end
-
-
1
private
-
-
1
def generate_failure_message
-
message = expected_collection_line
-
message += actual_collection_line
-
message += missing_elements_line unless missing_items.empty?
-
message += extra_elements_line unless extra_items.empty?
-
message
-
end
-
-
1
def expected_collection_line
-
message_line('expected collection contained', expected, true)
-
end
-
-
1
def actual_collection_line
-
message_line('actual collection contained', actual)
-
end
-
-
1
def missing_elements_line
-
message_line('the missing elements were', missing_items, true)
-
end
-
-
1
def extra_elements_line
-
message_line('the extra elements were', extra_items)
-
end
-
-
1
def describe_collection(collection, surface_descriptions=false)
-
if surface_descriptions
-
"#{description_of(safe_sort(surface_descriptions_in collection))}\n"
-
else
-
"#{description_of(safe_sort(collection))}\n"
-
end
-
end
-
-
1
def message_line(prefix, collection, surface_descriptions=false)
-
"%-32s%s" % [prefix + ':',
-
describe_collection(collection, surface_descriptions)]
-
end
-
-
1
def match(_expected, _actual)
-
return false unless convert_actual_to_an_array
-
match_when_sorted? || (extra_items.empty? && missing_items.empty?)
-
end
-
-
# This cannot always work (e.g. when dealing with unsortable items,
-
# or matchers as expected items), but it's practically free compared to
-
# the slowness of the full matching algorithm, and in common cases this
-
# works, so it's worth a try.
-
1
def match_when_sorted?
-
values_match?(safe_sort(expected), safe_sort(actual))
-
end
-
-
1
def convert_actual_to_an_array
-
if actual.respond_to?(:to_ary)
-
@actual = actual.to_ary
-
elsif should_enumerate?(actual) && actual.respond_to?(:to_a)
-
@actual = actual.to_a
-
else
-
return false
-
end
-
end
-
-
1
def safe_sort(array)
-
array.sort
-
rescue Support::AllExceptionsExceptOnesWeMustNotRescue
-
array
-
end
-
-
1
def missing_items
-
@missing_items ||= best_solution.unmatched_expected_indexes.map do |index|
-
expected[index]
-
end
-
end
-
-
1
def extra_items
-
@extra_items ||= best_solution.unmatched_actual_indexes.map do |index|
-
actual[index]
-
end
-
end
-
-
1
def best_solution
-
@best_solution ||= pairings_maximizer.find_best_solution
-
end
-
-
1
def pairings_maximizer
-
@pairings_maximizer ||= begin
-
expected_matches = Hash[Array.new(expected.size) { |i| [i, []] }]
-
actual_matches = Hash[Array.new(actual.size) { |i| [i, []] }]
-
-
expected.each_with_index do |e, ei|
-
actual.each_with_index do |a, ai|
-
next unless values_match?(e, a)
-
-
expected_matches[ei] << ai
-
actual_matches[ai] << ei
-
end
-
end
-
-
PairingsMaximizer.new(expected_matches, actual_matches)
-
end
-
end
-
-
# Once we started supporting composing matchers, the algorithm for this matcher got
-
# much more complicated. Consider this expression:
-
#
-
# expect(["fool", "food"]).to contain_exactly(/foo/, /fool/)
-
#
-
# This should pass (because we can pair /fool/ with "fool" and /foo/ with "food"), but
-
# the original algorithm used by this matcher would pair the first elements it could
-
# (/foo/ with "fool"), which would leave /fool/ and "food" unmatched. When we have
-
# an expected element which is a matcher that matches a superset of actual items
-
# compared to another expected element matcher, we need to consider every possible pairing.
-
#
-
# This class is designed to maximize the number of actual/expected pairings -- or,
-
# conversely, to minimize the number of unpaired items. It's essentially a brute
-
# force solution, but with a few heuristics applied to reduce the size of the
-
# problem space:
-
#
-
# * Any items which match none of the items in the other list are immediately
-
# placed into the `unmatched_expected_indexes` or `unmatched_actual_indexes` array.
-
# The extra items and missing items in the matcher failure message are derived
-
# from these arrays.
-
# * Any items which reciprocally match only each other are paired up and not
-
# considered further.
-
#
-
# What's left is only the items which match multiple items from the other list
-
# (or vice versa). From here, it performs a brute-force depth-first search,
-
# looking for a solution which pairs all elements in both lists, or, barring that,
-
# that produces the fewest unmatched items.
-
#
-
# @private
-
1
class PairingsMaximizer
-
1
Solution = Struct.new(:unmatched_expected_indexes, :unmatched_actual_indexes,
-
:indeterminate_expected_indexes, :indeterminate_actual_indexes) do
-
1
def worse_than?(other)
-
unmatched_item_count > other.unmatched_item_count
-
end
-
-
1
def candidate?
-
indeterminate_expected_indexes.empty? &&
-
indeterminate_actual_indexes.empty?
-
end
-
-
1
def ideal?
-
candidate? && (
-
unmatched_expected_indexes.empty? ||
-
unmatched_actual_indexes.empty?
-
)
-
end
-
-
1
def unmatched_item_count
-
unmatched_expected_indexes.count + unmatched_actual_indexes.count
-
end
-
-
1
def +(derived_candidate_solution)
-
self.class.new(
-
unmatched_expected_indexes + derived_candidate_solution.unmatched_expected_indexes,
-
unmatched_actual_indexes + derived_candidate_solution.unmatched_actual_indexes,
-
# Ignore the indeterminate indexes: by the time we get here,
-
# we've dealt with all indeterminates.
-
[], []
-
)
-
end
-
end
-
-
1
attr_reader :expected_to_actual_matched_indexes, :actual_to_expected_matched_indexes, :solution
-
-
1
def initialize(expected_to_actual_matched_indexes, actual_to_expected_matched_indexes)
-
@expected_to_actual_matched_indexes = expected_to_actual_matched_indexes
-
@actual_to_expected_matched_indexes = actual_to_expected_matched_indexes
-
-
unmatched_expected_indexes, indeterminate_expected_indexes =
-
categorize_indexes(expected_to_actual_matched_indexes, actual_to_expected_matched_indexes)
-
-
unmatched_actual_indexes, indeterminate_actual_indexes =
-
categorize_indexes(actual_to_expected_matched_indexes, expected_to_actual_matched_indexes)
-
-
@solution = Solution.new(unmatched_expected_indexes, unmatched_actual_indexes,
-
indeterminate_expected_indexes, indeterminate_actual_indexes)
-
end
-
-
1
def find_best_solution
-
return solution if solution.candidate?
-
best_solution_so_far = NullSolution
-
-
expected_index = solution.indeterminate_expected_indexes.first
-
actuals = expected_to_actual_matched_indexes[expected_index]
-
-
actuals.each do |actual_index|
-
solution = best_solution_for_pairing(expected_index, actual_index)
-
return solution if solution.ideal?
-
best_solution_so_far = solution if best_solution_so_far.worse_than?(solution)
-
end
-
-
best_solution_so_far
-
end
-
-
1
private
-
-
# @private
-
# Starting solution that is worse than any other real solution.
-
1
NullSolution = Class.new do
-
1
def self.worse_than?(_other)
-
true
-
end
-
end
-
-
1
def categorize_indexes(indexes_to_categorize, other_indexes)
-
unmatched = []
-
indeterminate = []
-
-
indexes_to_categorize.each_pair do |index, matches|
-
if matches.empty?
-
unmatched << index
-
elsif !reciprocal_single_match?(matches, index, other_indexes)
-
indeterminate << index
-
end
-
end
-
-
return unmatched, indeterminate
-
end
-
-
1
def reciprocal_single_match?(matches, index, other_list)
-
return false unless matches.one?
-
other_list[matches.first] == [index]
-
end
-
-
1
def best_solution_for_pairing(expected_index, actual_index)
-
modified_expecteds = apply_pairing_to(
-
solution.indeterminate_expected_indexes,
-
expected_to_actual_matched_indexes, actual_index)
-
-
modified_expecteds.delete(expected_index)
-
-
modified_actuals = apply_pairing_to(
-
solution.indeterminate_actual_indexes,
-
actual_to_expected_matched_indexes, expected_index)
-
-
modified_actuals.delete(actual_index)
-
-
solution + self.class.new(modified_expecteds, modified_actuals).find_best_solution
-
end
-
-
1
def apply_pairing_to(indeterminates, original_matches, other_list_index)
-
indeterminates.inject({}) do |accum, index|
-
accum[index] = original_matches[index] - [other_list_index]
-
accum
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
# Provides the implementation for `match`.
-
# Not intended to be instantiated directly.
-
1
class Match < BaseMatcher
-
1
def initialize(expected)
-
4
super(expected)
-
-
4
@expected_captures = nil
-
end
-
# @api private
-
# @return [String]
-
1
def description
-
if @expected_captures && @expected.match(actual)
-
"match #{surface_descriptions_in(expected).inspect} with captures #{surface_descriptions_in(@expected_captures).inspect}"
-
else
-
"match #{surface_descriptions_in(expected).inspect}"
-
end
-
end
-
-
# @api private
-
# @return [Boolean]
-
1
def diffable?
-
true
-
end
-
-
# Used to specify the captures we match against
-
# @return [self]
-
1
def with_captures(*captures)
-
@expected_captures = captures
-
self
-
end
-
-
1
private
-
-
1
def match(expected, actual)
-
4
return match_captures(expected, actual) if @expected_captures
-
4
return true if values_match?(expected, actual)
-
return false unless can_safely_call_match?(expected, actual)
-
actual.match(expected)
-
end
-
-
1
def can_safely_call_match?(expected, actual)
-
return false unless actual.respond_to?(:match)
-
-
!(RSpec::Matchers.is_a_matcher?(expected) &&
-
(String === actual || Regexp === actual))
-
end
-
-
1
def match_captures(expected, actual)
-
match = actual.match(expected)
-
if match
-
match = ReliableMatchData.new(match)
-
if match.names.empty?
-
values_match?(@expected_captures, match.captures)
-
else
-
expected_matcher = @expected_captures.last
-
values_match?(expected_matcher, Hash[match.names.zip(match.captures)]) ||
-
values_match?(expected_matcher, Hash[match.names.map(&:to_sym).zip(match.captures)]) ||
-
values_match?(@expected_captures, match.captures)
-
end
-
else
-
false
-
end
-
end
-
end
-
-
# @api private
-
# Used to wrap match data and make it reliable for 1.8.7
-
1
class ReliableMatchData
-
1
def initialize(match_data)
-
@match_data = match_data
-
end
-
-
1
if RUBY_VERSION == "1.8.7"
-
# @api private
-
# Returns match data names for named captures
-
# @return Array
-
def names
-
[]
-
end
-
else
-
# @api private
-
# Returns match data names for named captures
-
# @return Array
-
1
def names
-
match_data.names
-
end
-
end
-
-
# @api private
-
# returns an array of captures from the match data
-
# @return Array
-
1
def captures
-
match_data.captures
-
end
-
-
1
protected
-
-
1
attr_reader :match_data
-
end
-
end
-
end
-
end
-
1
require 'rspec/support'
-
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
# Provides the implementation for operator matchers.
-
# Not intended to be instantiated directly.
-
# Only available for use with `should`.
-
1
class OperatorMatcher
-
1
class << self
-
# @private
-
1
def registry
-
4
@registry ||= {}
-
end
-
-
# @private
-
1
def register(klass, operator, matcher)
-
2
registry[klass] ||= {}
-
2
registry[klass][operator] = matcher
-
end
-
-
# @private
-
1
def unregister(klass, operator)
-
registry[klass] && registry[klass].delete(operator)
-
end
-
-
# @private
-
1
def get(klass, operator)
-
klass.ancestors.each do |ancestor|
-
matcher = registry[ancestor] && registry[ancestor][operator]
-
return matcher if matcher
-
end
-
-
nil
-
end
-
end
-
-
1
register Enumerable, '=~', BuiltIn::ContainExactly
-
-
1
def initialize(actual)
-
@actual = actual
-
end
-
-
# @private
-
1
def self.use_custom_matcher_or_delegate(operator)
-
7
define_method(operator) do |expected|
-
if !has_non_generic_implementation_of?(operator) && (matcher = OperatorMatcher.get(@actual.class, operator))
-
@actual.__send__(::RSpec::Matchers.last_expectation_handler.should_method, matcher.new(expected))
-
else
-
eval_match(@actual, operator, expected)
-
end
-
end
-
-
7
negative_operator = operator.sub(/^=/, '!')
-
7
if negative_operator != operator && respond_to?(negative_operator)
-
2
define_method(negative_operator) do |_expected|
-
opposite_should = ::RSpec::Matchers.last_expectation_handler.opposite_should_method
-
raise "RSpec does not support `#{::RSpec::Matchers.last_expectation_handler.should_method} #{negative_operator} expected`. " \
-
"Use `#{opposite_should} #{operator} expected` instead."
-
end
-
end
-
end
-
-
1
['==', '===', '=~', '>', '>=', '<', '<='].each do |operator|
-
7
use_custom_matcher_or_delegate operator
-
end
-
-
# @private
-
1
def fail_with_message(message)
-
RSpec::Expectations.fail_with(message, @expected, @actual)
-
end
-
-
# @api private
-
# @return [String]
-
1
def description
-
"#{@operator} #{RSpec::Support::ObjectFormatter.format(@expected)}"
-
end
-
-
1
private
-
-
1
def has_non_generic_implementation_of?(op)
-
Support.method_handle_for(@actual, op).owner != ::Kernel
-
rescue NameError
-
false
-
end
-
-
1
def eval_match(actual, operator, expected)
-
::RSpec::Matchers.last_matcher = self
-
@operator, @expected = operator, expected
-
__delegate_operator(actual, operator, expected)
-
end
-
end
-
-
# @private
-
# Handles operator matcher for `should`.
-
1
class PositiveOperatorMatcher < OperatorMatcher
-
1
def __delegate_operator(actual, operator, expected)
-
if actual.__send__(operator, expected)
-
true
-
else
-
expected_formatted = RSpec::Support::ObjectFormatter.format(expected)
-
actual_formatted = RSpec::Support::ObjectFormatter.format(actual)
-
-
if ['==', '===', '=~'].include?(operator)
-
fail_with_message("expected: #{expected_formatted}\n got: #{actual_formatted} (using #{operator})")
-
else
-
fail_with_message("expected: #{operator} #{expected_formatted}\n got: #{operator.gsub(/./, ' ')} #{actual_formatted}")
-
end
-
end
-
end
-
end
-
-
# @private
-
# Handles operator matcher for `should_not`.
-
1
class NegativeOperatorMatcher < OperatorMatcher
-
1
def __delegate_operator(actual, operator, expected)
-
return false unless actual.__send__(operator, expected)
-
-
expected_formatted = RSpec::Support::ObjectFormatter.format(expected)
-
actual_formatted = RSpec::Support::ObjectFormatter.format(actual)
-
-
fail_with_message("expected not: #{operator} #{expected_formatted}\n got: #{operator.gsub(/./, ' ')} #{actual_formatted}")
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_support "fuzzy_matcher"
-
-
1
module RSpec
-
1
module Matchers
-
# Mixin designed to support the composable matcher features
-
# of RSpec 3+. Mix it into your custom matcher classes to
-
# allow them to be used in a composable fashion.
-
#
-
# @api public
-
1
module Composable
-
# Creates a compound `and` expectation. The matcher will
-
# only pass if both sub-matchers pass.
-
# This can be chained together to form an arbitrarily long
-
# chain of matchers.
-
#
-
# @example
-
# expect(alphabet).to start_with("a").and end_with("z")
-
# expect(alphabet).to start_with("a") & end_with("z")
-
#
-
# @note The negative form (`expect(...).not_to matcher.and other`)
-
# is not supported at this time.
-
1
def and(matcher)
-
BuiltIn::Compound::And.new self, matcher
-
end
-
1
alias & and
-
-
# Creates a compound `or` expectation. The matcher will
-
# pass if either sub-matcher passes.
-
# This can be chained together to form an arbitrarily long
-
# chain of matchers.
-
#
-
# @example
-
# expect(stoplight.color).to eq("red").or eq("green").or eq("yellow")
-
# expect(stoplight.color).to eq("red") | eq("green") | eq("yellow")
-
#
-
# @note The negative form (`expect(...).not_to matcher.or other`)
-
# is not supported at this time.
-
1
def or(matcher)
-
BuiltIn::Compound::Or.new self, matcher
-
end
-
1
alias | or
-
-
# Delegates to `#matches?`. Allows matchers to be used in composable
-
# fashion and also supports using matchers in case statements.
-
1
def ===(value)
-
matches?(value)
-
end
-
-
1
private
-
-
# This provides a generic way to fuzzy-match an expected value against
-
# an actual value. It understands nested data structures (e.g. hashes
-
# and arrays) and is able to match against a matcher being used as
-
# the expected value or within the expected value at any level of
-
# nesting.
-
#
-
# Within a custom matcher you are encouraged to use this whenever your
-
# matcher needs to match two values, unless it needs more precise semantics.
-
# For example, the `eq` matcher _does not_ use this as it is meant to
-
# use `==` (and only `==`) for matching.
-
#
-
# @param expected [Object] what is expected
-
# @param actual [Object] the actual value
-
#
-
# @!visibility public
-
1
def values_match?(expected, actual)
-
4
expected = with_matchers_cloned(expected)
-
4
Support::FuzzyMatcher.values_match?(expected, actual)
-
end
-
-
# Returns the description of the given object in a way that is
-
# aware of composed matchers. If the object is a matcher with
-
# a `description` method, returns the description; otherwise
-
# returns `object.inspect`.
-
#
-
# You are encouraged to use this in your custom matcher's
-
# `description`, `failure_message` or
-
# `failure_message_when_negated` implementation if you are
-
# supporting matcher arguments.
-
#
-
# @!visibility public
-
1
def description_of(object)
-
RSpec::Support::ObjectFormatter.format(object)
-
end
-
-
# Transforms the given data structue (typically a hash or array)
-
# into a new data structure that, when `#inspect` is called on it,
-
# will provide descriptions of any contained matchers rather than
-
# the normal `#inspect` output.
-
#
-
# You are encouraged to use this in your custom matcher's
-
# `description`, `failure_message` or
-
# `failure_message_when_negated` implementation if you are
-
# supporting any arguments which may be a data structure
-
# containing matchers.
-
#
-
# @!visibility public
-
1
def surface_descriptions_in(item)
-
if Matchers.is_a_describable_matcher?(item)
-
DescribableItem.new(item)
-
elsif Hash === item
-
Hash[surface_descriptions_in(item.to_a)]
-
elsif Struct === item || unreadable_io?(item)
-
RSpec::Support::ObjectFormatter.format(item)
-
elsif should_enumerate?(item)
-
item.map { |subitem| surface_descriptions_in(subitem) }
-
else
-
item
-
end
-
end
-
-
# @private
-
# Historically, a single matcher instance was only checked
-
# against a single value. Given that the matcher was only
-
# used once, it's been common to memoize some intermediate
-
# calculation that is derived from the `actual` value in
-
# order to reuse that intermediate result in the failure
-
# message.
-
#
-
# This can cause a problem when using such a matcher as an
-
# argument to another matcher in a composed matcher expression,
-
# since the matcher instance may be checked against multiple
-
# values and produce invalid results due to the memoization.
-
#
-
# To deal with this, we clone any matchers in `expected` via
-
# this method when using `values_match?`, so that any memoization
-
# does not "leak" between checks.
-
1
def with_matchers_cloned(object)
-
4
if Matchers.is_a_matcher?(object)
-
object.clone
-
4
elsif Hash === object
-
Hash[with_matchers_cloned(object.to_a)]
-
4
elsif Struct === object || unreadable_io?(object)
-
object
-
4
elsif should_enumerate?(object)
-
object.map { |subobject| with_matchers_cloned(subobject) }
-
else
-
4
object
-
end
-
end
-
-
1
if String.ancestors.include?(Enumerable) # 1.8.7
-
# :nocov:
-
skipped
# Strings are not enumerable on 1.9, and on 1.8 they are an infinitely
-
skipped
# nested enumerable: since ruby lacks a character class, it yields
-
skipped
# 1-character strings, which are themselves enumerable, composed of a
-
skipped
# a single 1-character string, which is an enumerable, etc.
-
skipped
#
-
skipped
# @api private
-
skipped
def should_enumerate?(item)
-
skipped
return false if String === item
-
skipped
Enumerable === item && !(Range === item) && item.none? { |subitem| subitem.equal?(item) }
-
skipped
end
-
# :nocov:
-
else
-
# @api private
-
1
def should_enumerate?(item)
-
4
Enumerable === item && !(Range === item) && item.none? { |subitem| subitem.equal?(item) }
-
end
-
end
-
-
# @api private
-
1
def unreadable_io?(object)
-
4
return false unless IO === object
-
object.each {} # STDOUT is enumerable but raises an error
-
false
-
rescue IOError
-
true
-
end
-
1
module_function :surface_descriptions_in, :should_enumerate?, :unreadable_io?
-
-
# Wraps an item in order to surface its `description` via `inspect`.
-
# @api private
-
1
DescribableItem = Struct.new(:item) do
-
1
def inspect
-
"(#{item.description})"
-
end
-
-
1
def pretty_print(pp)
-
pp.text "(#{item.description})"
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
# Defines the custom matcher DSL.
-
1
module DSL
-
# Defines a custom matcher.
-
# @see RSpec::Matchers
-
1
def define(name, &declarations)
-
warn_about_block_args(name, declarations)
-
define_method name do |*expected, &block_arg|
-
RSpec::Matchers::DSL::Matcher.new(name, declarations, self, *expected, &block_arg)
-
end
-
end
-
1
alias_method :matcher, :define
-
-
1
private
-
-
1
if Proc.method_defined?(:parameters)
-
1
def warn_about_block_args(name, declarations)
-
declarations.parameters.each do |type, arg_name|
-
next unless type == :block
-
RSpec.warning("Your `#{name}` custom matcher receives a block argument (`#{arg_name}`), " \
-
"but due to limitations in ruby, RSpec cannot provide the block. Instead, " \
-
"use the `block_arg` method to access the block")
-
end
-
end
-
else
-
# :nocov:
-
skipped
def warn_about_block_args(*)
-
skipped
# There's no way to detect block params on 1.8 since the method reflection APIs don't expose it
-
skipped
end
-
# :nocov:
-
end
-
-
2
RSpec.configure { |c| c.extend self } if RSpec.respond_to?(:configure)
-
-
# Contains the methods that are available from within the
-
# `RSpec::Matchers.define` DSL for creating custom matchers.
-
1
module Macros
-
# Stores the block that is used to determine whether this matcher passes
-
# or fails. The block should return a boolean value. When the matcher is
-
# passed to `expect(...).to` and the block returns `true`, then the expectation
-
# passes. Similarly, when the matcher is passed to `expect(...).not_to` and the
-
# block returns `false`, then the expectation passes.
-
#
-
# @example
-
#
-
# RSpec::Matchers.define :be_even do
-
# match do |actual|
-
# actual.even?
-
# end
-
# end
-
#
-
# expect(4).to be_even # passes
-
# expect(3).not_to be_even # passes
-
# expect(3).to be_even # fails
-
# expect(4).not_to be_even # fails
-
#
-
# @yield [Object] actual the actual value (i.e. the value wrapped by `expect`)
-
1
def match(&match_block)
-
define_user_override(:matches?, match_block) do |actual|
-
begin
-
@actual = actual
-
RSpec::Support.with_failure_notifier(RAISE_NOTIFIER) do
-
super(*actual_arg_for(match_block))
-
end
-
rescue RSpec::Expectations::ExpectationNotMetError
-
false
-
end
-
end
-
end
-
-
# @private
-
1
RAISE_NOTIFIER = Proc.new { |err, _opts| raise err }
-
-
# Use this to define the block for a negative expectation (`expect(...).not_to`)
-
# when the positive and negative forms require different handling. This
-
# is rarely necessary, but can be helpful, for example, when specifying
-
# asynchronous processes that require different timeouts.
-
#
-
# @yield [Object] actual the actual value (i.e. the value wrapped by `expect`)
-
1
def match_when_negated(&match_block)
-
define_user_override(:does_not_match?, match_block) do |actual|
-
@actual = actual
-
super(*actual_arg_for(match_block))
-
end
-
end
-
-
# Use this instead of `match` when the block will raise an exception
-
# rather than returning false to indicate a failure.
-
#
-
# @example
-
#
-
# RSpec::Matchers.define :accept_as_valid do |candidate_address|
-
# match_unless_raises ValidationException do |validator|
-
# validator.validate(candidate_address)
-
# end
-
# end
-
#
-
# expect(email_validator).to accept_as_valid("person@company.com")
-
#
-
# @yield [Object] actual the actual object (i.e. the value wrapped by `expect`)
-
1
def match_unless_raises(expected_exception=Exception, &match_block)
-
define_user_override(:matches?, match_block) do |actual|
-
@actual = actual
-
begin
-
super(*actual_arg_for(match_block))
-
rescue expected_exception => @rescued_exception
-
false
-
else
-
true
-
end
-
end
-
end
-
-
# Customizes the failure messsage to use when this matcher is
-
# asked to positively match. Only use this when the message
-
# generated by default doesn't suit your needs.
-
#
-
# @example
-
#
-
# RSpec::Matchers.define :have_strength do |expected|
-
# match { your_match_logic }
-
#
-
# failure_message do |actual|
-
# "Expected strength of #{expected}, but had #{actual.strength}"
-
# end
-
# end
-
#
-
# @yield [Object] actual the actual object (i.e. the value wrapped by `expect`)
-
1
def failure_message(&definition)
-
define_user_override(__method__, definition)
-
end
-
-
# Customize the failure messsage to use when this matcher is asked
-
# to negatively match. Only use this when the message generated by
-
# default doesn't suit your needs.
-
#
-
# @example
-
#
-
# RSpec::Matchers.define :have_strength do |expected|
-
# match { your_match_logic }
-
#
-
# failure_message_when_negated do |actual|
-
# "Expected not to have strength of #{expected}, but did"
-
# end
-
# end
-
#
-
# @yield [Object] actual the actual object (i.e. the value wrapped by `expect`)
-
1
def failure_message_when_negated(&definition)
-
define_user_override(__method__, definition)
-
end
-
-
# Customize the description to use for one-liners. Only use this when
-
# the description generated by default doesn't suit your needs.
-
#
-
# @example
-
#
-
# RSpec::Matchers.define :qualify_for do |expected|
-
# match { your_match_logic }
-
#
-
# description do
-
# "qualify for #{expected}"
-
# end
-
# end
-
#
-
# @yield [Object] actual the actual object (i.e. the value wrapped by `expect`)
-
1
def description(&definition)
-
define_user_override(__method__, definition)
-
end
-
-
# Tells the matcher to diff the actual and expected values in the failure
-
# message.
-
1
def diffable
-
define_method(:diffable?) { true }
-
end
-
-
# Declares that the matcher can be used in a block expectation.
-
# Users will not be able to use your matcher in a block
-
# expectation without declaring this.
-
# (e.g. `expect { do_something }.to matcher`).
-
1
def supports_block_expectations
-
define_method(:supports_block_expectations?) { true }
-
end
-
-
# Convenience for defining methods on this matcher to create a fluent
-
# interface. The trick about fluent interfaces is that each method must
-
# return self in order to chain methods together. `chain` handles that
-
# for you. If the method is invoked and the
-
# `include_chain_clauses_in_custom_matcher_descriptions` config option
-
# hash been enabled, the chained method name and args will be added to the
-
# default description and failure message.
-
#
-
# In the common case where you just want the chained method to store some
-
# value(s) for later use (e.g. in `match`), you can provide one or more
-
# attribute names instead of a block; the chained method will store its
-
# arguments in instance variables with those names, and the values will
-
# be exposed via getters.
-
#
-
# @example
-
#
-
# RSpec::Matchers.define :have_errors_on do |key|
-
# chain :with do |message|
-
# @message = message
-
# end
-
#
-
# match do |actual|
-
# actual.errors[key] == @message
-
# end
-
# end
-
#
-
# expect(minor).to have_errors_on(:age).with("Not old enough to participate")
-
1
def chain(method_name, *attr_names, &definition)
-
unless block_given? ^ attr_names.any?
-
raise ArgumentError, "You must pass either a block or some attribute names (but not both) to `chain`."
-
end
-
-
definition = assign_attributes(attr_names) if attr_names.any?
-
-
define_user_override(method_name, definition) do |*args, &block|
-
super(*args, &block)
-
@chained_method_clauses.push([method_name, args])
-
self
-
end
-
end
-
-
1
def assign_attributes(attr_names)
-
attr_reader(*attr_names)
-
private(*attr_names)
-
-
lambda do |*attr_values|
-
attr_names.zip(attr_values) do |attr_name, attr_value|
-
instance_variable_set(:"@#{attr_name}", attr_value)
-
end
-
end
-
end
-
-
# assign_attributes isn't defined in the private section below because
-
# that makes MRI 1.9.2 emit a warning about private attributes.
-
1
private :assign_attributes
-
-
1
private
-
-
# Does the following:
-
#
-
# - Defines the named method using a user-provided block
-
# in @user_method_defs, which is included as an ancestor
-
# in the singleton class in which we eval the `define` block.
-
# - Defines an overriden definition for the same method
-
# usign the provided `our_def` block.
-
# - Provides a default `our_def` block for the common case
-
# of needing to call the user's definition with `@actual`
-
# as an arg, but only if their block's arity can handle it.
-
#
-
# This compiles the user block into an actual method, allowing
-
# them to use normal method constructs like `return`
-
# (e.g. for a early guard statement), while allowing us to define
-
# an override that can provide the wrapped handling
-
# (e.g. assigning `@actual`, rescueing errors, etc) and
-
# can `super` to the user's definition.
-
1
def define_user_override(method_name, user_def, &our_def)
-
@user_method_defs.__send__(:define_method, method_name, &user_def)
-
our_def ||= lambda { super(*actual_arg_for(user_def)) }
-
define_method(method_name, &our_def)
-
end
-
-
# Defines deprecated macro methods from RSpec 2 for backwards compatibility.
-
# @deprecated Use the methods from {Macros} instead.
-
1
module Deprecated
-
# @deprecated Use {Macros#match} instead.
-
1
def match_for_should(&definition)
-
RSpec.deprecate("`match_for_should`", :replacement => "`match`")
-
match(&definition)
-
end
-
-
# @deprecated Use {Macros#match_when_negated} instead.
-
1
def match_for_should_not(&definition)
-
RSpec.deprecate("`match_for_should_not`", :replacement => "`match_when_negated`")
-
match_when_negated(&definition)
-
end
-
-
# @deprecated Use {Macros#failure_message} instead.
-
1
def failure_message_for_should(&definition)
-
RSpec.deprecate("`failure_message_for_should`", :replacement => "`failure_message`")
-
failure_message(&definition)
-
end
-
-
# @deprecated Use {Macros#failure_message_when_negated} instead.
-
1
def failure_message_for_should_not(&definition)
-
RSpec.deprecate("`failure_message_for_should_not`", :replacement => "`failure_message_when_negated`")
-
failure_message_when_negated(&definition)
-
end
-
end
-
end
-
-
# Defines default implementations of the matcher
-
# protocol methods for custom matchers. You can
-
# override any of these using the {RSpec::Matchers::DSL::Macros Macros} methods
-
# from within an `RSpec::Matchers.define` block.
-
1
module DefaultImplementations
-
1
include BuiltIn::BaseMatcher::DefaultFailureMessages
-
-
# @api private
-
# Used internally by objects returns by `should` and `should_not`.
-
1
def diffable?
-
false
-
end
-
-
# The default description.
-
1
def description
-
english_name = EnglishPhrasing.split_words(name)
-
expected_list = EnglishPhrasing.list(expected)
-
"#{english_name}#{expected_list}#{chained_method_clause_sentences}"
-
end
-
-
# Matchers do not support block expectations by default. You
-
# must opt-in.
-
1
def supports_block_expectations?
-
false
-
end
-
-
# Most matchers do not expect call stack jumps.
-
1
def expects_call_stack_jump?
-
false
-
end
-
-
1
private
-
-
1
def chained_method_clause_sentences
-
return '' unless Expectations.configuration.include_chain_clauses_in_custom_matcher_descriptions?
-
-
@chained_method_clauses.map do |(method_name, method_args)|
-
english_name = EnglishPhrasing.split_words(method_name)
-
arg_list = EnglishPhrasing.list(method_args)
-
" #{english_name}#{arg_list}"
-
end.join
-
end
-
end
-
-
# The class used for custom matchers. The block passed to
-
# `RSpec::Matchers.define` will be evaluated in the context
-
# of the singleton class of an instance, and will have the
-
# {RSpec::Matchers::DSL::Macros Macros} methods available.
-
1
class Matcher
-
# Provides default implementations for the matcher protocol methods.
-
1
include DefaultImplementations
-
-
# Allows expectation expressions to be used in the match block.
-
1
include RSpec::Matchers
-
-
# Supports the matcher composability features of RSpec 3+.
-
1
include Composable
-
-
# Makes the macro methods available to an `RSpec::Matchers.define` block.
-
1
extend Macros
-
1
extend Macros::Deprecated
-
-
# Exposes the value being matched against -- generally the object
-
# object wrapped by `expect`.
-
1
attr_reader :actual
-
-
# Exposes the exception raised during the matching by `match_unless_raises`.
-
# Could be useful to extract details for a failure message.
-
1
attr_reader :rescued_exception
-
-
# The block parameter used in the expectation
-
1
attr_reader :block_arg
-
-
# The name of the matcher.
-
1
attr_reader :name
-
-
# @api private
-
1
def initialize(name, declarations, matcher_execution_context, *expected, &block_arg)
-
@name = name
-
@actual = nil
-
@expected_as_array = expected
-
@matcher_execution_context = matcher_execution_context
-
@chained_method_clauses = []
-
@block_arg = block_arg
-
-
class << self
-
# See `Macros#define_user_override` above, for an explanation.
-
include(@user_method_defs = Module.new)
-
self
-
end.class_exec(*expected, &declarations)
-
end
-
-
# Provides the expected value. This will return an array if
-
# multiple arguments were passed to the matcher; otherwise it
-
# will return a single value.
-
# @see #expected_as_array
-
1
def expected
-
if expected_as_array.size == 1
-
expected_as_array[0]
-
else
-
expected_as_array
-
end
-
end
-
-
# Returns the expected value as an an array. This exists primarily
-
# to aid in upgrading from RSpec 2.x, since in RSpec 2, `expected`
-
# always returned an array.
-
# @see #expected
-
1
attr_reader :expected_as_array
-
-
# Adds the name (rather than a cryptic hex number)
-
# so we can identify an instance of
-
# the matcher in error messages (e.g. for `NoMethodError`)
-
1
def inspect
-
"#<#{self.class.name} #{name}>"
-
end
-
-
1
if RUBY_VERSION.to_f >= 1.9
-
# Indicates that this matcher responds to messages
-
# from the `@matcher_execution_context` as well.
-
# Also, supports getting a method object for such methods.
-
1
def respond_to_missing?(method, include_private=false)
-
super || @matcher_execution_context.respond_to?(method, include_private)
-
end
-
else # for 1.8.7
-
# :nocov:
-
skipped
# Indicates that this matcher responds to messages
-
skipped
# from the `@matcher_execution_context` as well.
-
skipped
def respond_to?(method, include_private=false)
-
skipped
super || @matcher_execution_context.respond_to?(method, include_private)
-
skipped
end
-
# :nocov:
-
end
-
-
1
private
-
-
1
def actual_arg_for(block)
-
block.arity.zero? ? [] : [@actual]
-
end
-
-
# Takes care of forwarding unhandled messages to the
-
# `@matcher_execution_context` (typically the current
-
# running `RSpec::Core::Example`). This is needed by
-
# rspec-rails so that it can define matchers that wrap
-
# Rails' test helper methods, but it's also a useful
-
# feature in its own right.
-
1
def method_missing(method, *args, &block)
-
if @matcher_execution_context.respond_to?(method)
-
@matcher_execution_context.__send__ method, *args, &block
-
else
-
super(method, *args, &block)
-
end
-
end
-
end
-
end
-
end
-
end
-
-
1
RSpec::Matchers.extend RSpec::Matchers::DSL
-
1
module RSpec
-
1
module Matchers
-
# Facilitates converting ruby objects to English phrases.
-
1
module EnglishPhrasing
-
# Converts a symbol into an English expression.
-
#
-
# split_words(:banana_creme_pie) #=> "banana creme pie"
-
#
-
1
def self.split_words(sym)
-
sym.to_s.gsub(/_/, ' ')
-
end
-
-
# @note The returned string has a leading space except
-
# when given an empty list.
-
#
-
# Converts an object (often a collection of objects)
-
# into an English list.
-
#
-
# list(['banana', 'kiwi', 'mango'])
-
# #=> " \"banana\", \"kiwi\", and \"mango\""
-
#
-
# Given an empty collection, returns the empty string.
-
#
-
# list([]) #=> ""
-
#
-
1
def self.list(obj)
-
return " #{RSpec::Support::ObjectFormatter.format(obj)}" if !obj || Struct === obj
-
items = Array(obj).map { |w| RSpec::Support::ObjectFormatter.format(w) }
-
case items.length
-
when 0
-
""
-
when 1
-
" #{items[0]}"
-
when 2
-
" #{items[0]} and #{items[1]}"
-
else
-
" #{items[0...-1].join(', ')}, and #{items[-1]}"
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
# @api private
-
# Handles list of expected values when there is a need to render
-
# multiple diffs. Also can handle one value.
-
1
class ExpectedsForMultipleDiffs
-
# @private
-
# Default diff label when there is only one matcher in diff
-
# output
-
1
DEFAULT_DIFF_LABEL = "Diff:".freeze
-
-
# @private
-
# Maximum readable matcher description length
-
1
DESCRIPTION_MAX_LENGTH = 65
-
-
1
def initialize(expected_list)
-
@expected_list = expected_list
-
end
-
-
# @api private
-
# Wraps provided expected value in instance of
-
# ExpectedForMultipleDiffs. If provided value is already an
-
# ExpectedForMultipleDiffs then it just returns it.
-
# @param [Any] expected value to be wrapped
-
# @return [RSpec::Matchers::ExpectedsForMultipleDiffs]
-
1
def self.from(expected)
-
return expected if self === expected
-
new([[expected, DEFAULT_DIFF_LABEL]])
-
end
-
-
# @api private
-
# Wraps provided matcher list in instance of
-
# ExpectedForMultipleDiffs.
-
# @param [Array<Any>] matchers list of matchers to wrap
-
# @return [RSpec::Matchers::ExpectedsForMultipleDiffs]
-
1
def self.for_many_matchers(matchers)
-
new(matchers.map { |m| [m.expected, diff_label_for(m)] })
-
end
-
-
# @api private
-
# Returns message with diff(s) appended for provided differ
-
# factory and actual value if there are any
-
# @param [String] message original failure message
-
# @param [Proc] differ
-
# @param [Any] actual value
-
# @return [String]
-
1
def message_with_diff(message, differ, actual)
-
diff = diffs(differ, actual)
-
message = "#{message}\n#{diff}" unless diff.empty?
-
message
-
end
-
-
1
private
-
-
1
def self.diff_label_for(matcher)
-
"Diff for (#{truncated(RSpec::Support::ObjectFormatter.format(matcher))}):"
-
end
-
-
1
def self.truncated(description)
-
return description if description.length <= DESCRIPTION_MAX_LENGTH
-
description[0...DESCRIPTION_MAX_LENGTH - 3] << "..."
-
end
-
-
1
def diffs(differ, actual)
-
@expected_list.map do |(expected, diff_label)|
-
diff = differ.diff(actual, expected)
-
next if diff.strip.empty?
-
"#{diff_label}#{diff}"
-
end.compact.join("\n")
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
class << self
-
# @private
-
1
attr_accessor :last_matcher, :last_expectation_handler
-
end
-
-
# @api private
-
# Used by rspec-core to clear the state used to generate
-
# descriptions after an example.
-
1
def self.clear_generated_description
-
28
self.last_matcher = nil
-
28
self.last_expectation_handler = nil
-
end
-
-
# @api private
-
# Generates an an example description based on the last expectation.
-
# Used by rspec-core's one-liner syntax.
-
1
def self.generated_description
-
1
return nil if last_expectation_handler.nil?
-
1
"#{last_expectation_handler.verb} #{last_description}"
-
end
-
-
1
private
-
-
1
def self.last_description
-
1
last_matcher.respond_to?(:description) ? last_matcher.description : <<-MESSAGE
-
When you call a matcher in an example without a String, like this:
-
-
specify { expect(object).to matcher }
-
-
or this:
-
-
it { is_expected.to matcher }
-
-
RSpec expects the matcher to have a #description method. You should either
-
add a String to the example this matcher is being used in, or give it a
-
description method. Then you won't have to suffer this lengthy warning again.
-
MESSAGE
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
# Provides the necessary plumbing to wrap a matcher with a decorator.
-
# @private
-
1
class MatcherDelegator
-
1
include Composable
-
1
attr_reader :base_matcher
-
-
1
def initialize(base_matcher)
-
@base_matcher = base_matcher
-
end
-
-
1
def method_missing(*args, &block)
-
base_matcher.__send__(*args, &block)
-
end
-
-
1
if ::RUBY_VERSION.to_f > 1.8
-
1
def respond_to_missing?(name, include_all=false)
-
super || base_matcher.respond_to?(name, include_all)
-
end
-
else
-
# :nocov:
-
skipped
def respond_to?(name, include_all=false)
-
skipped
super || base_matcher.respond_to?(name, include_all)
-
skipped
end
-
# :nocov:
-
end
-
-
1
def initialize_copy(other)
-
@base_matcher = @base_matcher.clone
-
super
-
end
-
end
-
end
-
end
-
1
require 'rspec/support'
-
1
RSpec::Support.require_rspec_support 'caller_filter'
-
1
RSpec::Support.require_rspec_support 'warnings'
-
1
RSpec::Support.require_rspec_support 'ruby_features'
-
-
22
RSpec::Support.define_optimized_require_for_rspec(:mocks) { |f| require_relative f }
-
-
%w[
-
instance_method_stasher
-
method_double
-
argument_matchers
-
example_methods
-
proxy
-
test_double
-
argument_list_matcher
-
message_expectation
-
order_group
-
error_generator
-
space
-
mutate_const
-
targets
-
syntax
-
configuration
-
verifying_double
-
version
-
18
].each { |name| RSpec::Support.require_rspec_mocks name }
-
-
# Share the top-level RSpec namespace, because we are a core supported
-
# extension.
-
1
module RSpec
-
# Contains top-level utility methods. While this contains a few
-
# public methods, these are not generally meant to be called from
-
# a test or example. They exist primarily for integration with
-
# test frameworks (such as rspec-core).
-
1
module Mocks
-
# Performs per-test/example setup. This should be called before
-
# an test or example begins.
-
1
def self.setup
-
28
@space_stack << (@space = space.new_scope)
-
end
-
-
# Verifies any message expectations that were set during the
-
# test or example. This should be called at the end of an example.
-
1
def self.verify
-
28
space.verify_all
-
end
-
-
# Cleans up all test double state (including any methods that were
-
# redefined on partial doubles). This _must_ be called after
-
# each example, even if an error was raised during the example.
-
1
def self.teardown
-
28
space.reset_all
-
28
@space_stack.pop
-
28
@space = @space_stack.last || @root_space
-
end
-
-
# Adds an allowance (stub) on `subject`
-
#
-
# @param subject the subject to which the message will be added
-
# @param message a symbol, representing the message that will be
-
# added.
-
# @param opts a hash of options, :expected_from is used to set the
-
# original call site
-
# @yield an optional implementation for the allowance
-
#
-
# @example Defines the implementation of `foo` on `bar`, using the passed block
-
# x = 0
-
# RSpec::Mocks.allow_message(bar, :foo) { x += 1 }
-
1
def self.allow_message(subject, message, opts={}, &block)
-
space.proxy_for(subject).add_stub(message, opts, &block)
-
end
-
-
# Sets a message expectation on `subject`.
-
# @param subject the subject on which the message will be expected
-
# @param message a symbol, representing the message that will be
-
# expected.
-
# @param opts a hash of options, :expected_from is used to set the
-
# original call site
-
# @yield an optional implementation for the expectation
-
#
-
# @example Expect the message `foo` to receive `bar`, then call it
-
# RSpec::Mocks.expect_message(bar, :foo)
-
# bar.foo
-
1
def self.expect_message(subject, message, opts={}, &block)
-
space.proxy_for(subject).add_message_expectation(message, opts, &block)
-
end
-
-
# Call the passed block and verify mocks after it has executed. This allows
-
# mock usage in arbitrary places, such as a `before(:all)` hook.
-
1
def self.with_temporary_scope
-
setup
-
-
begin
-
yield
-
verify
-
ensure
-
teardown
-
end
-
end
-
-
1
class << self
-
# @private
-
1
attr_reader :space
-
end
-
1
@space_stack = []
-
1
@root_space = @space = RSpec::Mocks::RootSpace.new
-
-
# @private
-
1
IGNORED_BACKTRACE_LINE = 'this backtrace line is ignored'
-
-
# To speed up boot time a bit, delay loading optional or rarely
-
# used features until their first use.
-
1
autoload :AnyInstance, "rspec/mocks/any_instance"
-
1
autoload :ExpectChain, "rspec/mocks/message_chain"
-
1
autoload :StubChain, "rspec/mocks/message_chain"
-
1
autoload :MarshalExtension, "rspec/mocks/marshal_extension"
-
-
# Namespace for mock-related matchers.
-
1
module Matchers
-
1
autoload :HaveReceived, "rspec/mocks/matchers/have_received"
-
1
autoload :Receive, "rspec/mocks/matchers/receive"
-
1
autoload :ReceiveMessageChain, "rspec/mocks/matchers/receive_message_chain"
-
1
autoload :ReceiveMessages, "rspec/mocks/matchers/receive_messages"
-
end
-
end
-
end
-
# We intentionally do not use the `RSpec::Support.require...` methods
-
# here so that this file can be loaded individually, as documented
-
# below.
-
1
require 'rspec/mocks/argument_matchers'
-
1
require 'rspec/support/fuzzy_matcher'
-
-
1
module RSpec
-
1
module Mocks
-
# Wrapper for matching arguments against a list of expected values. Used by
-
# the `with` method on a `MessageExpectation`:
-
#
-
# expect(object).to receive(:message).with(:a, 'b', 3)
-
# object.message(:a, 'b', 3)
-
#
-
# Values passed to `with` can be literal values or argument matchers that
-
# match against the real objects .e.g.
-
#
-
# expect(object).to receive(:message).with(hash_including(:a => 'b'))
-
#
-
# Can also be used directly to match the contents of any `Array`. This
-
# enables 3rd party mocking libs to take advantage of rspec's argument
-
# matching without using the rest of rspec-mocks.
-
#
-
# require 'rspec/mocks/argument_list_matcher'
-
# include RSpec::Mocks::ArgumentMatchers
-
#
-
# arg_list_matcher = RSpec::Mocks::ArgumentListMatcher.new(123, hash_including(:a => 'b'))
-
# arg_list_matcher.args_match?(123, :a => 'b')
-
#
-
# This class is immutable.
-
#
-
# @see ArgumentMatchers
-
1
class ArgumentListMatcher
-
# @private
-
1
attr_reader :expected_args
-
-
# @api public
-
# @param [Array] expected_args a list of expected literals and/or argument matchers
-
#
-
# Initializes an `ArgumentListMatcher` with a collection of literal
-
# values and/or argument matchers.
-
#
-
# @see ArgumentMatchers
-
# @see #args_match?
-
1
def initialize(*expected_args)
-
1
@expected_args = expected_args
-
1
ensure_expected_args_valid!
-
end
-
-
# @api public
-
# @param [Array] args
-
#
-
# Matches each element in the `expected_args` against the element in the same
-
# position of the arguments passed to `new`.
-
#
-
# @see #initialize
-
1
def args_match?(*args)
-
Support::FuzzyMatcher.values_match?(resolve_expected_args_based_on(args), args)
-
end
-
-
# @private
-
# Resolves abstract arg placeholders like `no_args` and `any_args` into
-
# a more concrete arg list based on the provided `actual_args`.
-
1
def resolve_expected_args_based_on(actual_args)
-
return [] if [ArgumentMatchers::NoArgsMatcher::INSTANCE] == expected_args
-
-
any_args_index = expected_args.index { |a| ArgumentMatchers::AnyArgsMatcher::INSTANCE == a }
-
return expected_args unless any_args_index
-
-
replace_any_args_with_splat_of_anything(any_args_index, actual_args.count)
-
end
-
-
1
private
-
-
1
def replace_any_args_with_splat_of_anything(before_count, actual_args_count)
-
any_args_count = actual_args_count - expected_args.count + 1
-
after_count = expected_args.count - before_count - 1
-
-
any_args = 1.upto(any_args_count).map { ArgumentMatchers::AnyArgMatcher::INSTANCE }
-
expected_args.first(before_count) + any_args + expected_args.last(after_count)
-
end
-
-
1
def ensure_expected_args_valid!
-
2
if expected_args.count { |a| ArgumentMatchers::AnyArgsMatcher::INSTANCE == a } > 1
-
raise ArgumentError, "`any_args` can only be passed to " \
-
"`with` once but you have passed it multiple times."
-
1
elsif expected_args.count > 1 && expected_args.any? { |a| ArgumentMatchers::NoArgsMatcher::INSTANCE == a }
-
raise ArgumentError, "`no_args` can only be passed as a " \
-
"singleton argument to `with` (i.e. `with(no_args)`), " \
-
"but you have passed additional arguments."
-
end
-
end
-
-
# Value that will match all argument lists.
-
#
-
# @private
-
1
MATCH_ALL = new(ArgumentMatchers::AnyArgsMatcher::INSTANCE)
-
end
-
end
-
end
-
# This cannot take advantage of our relative requires, since this file is a
-
# dependency of `rspec/mocks/argument_list_matcher.rb`. See comment there for
-
# details.
-
1
require 'rspec/support/matcher_definition'
-
-
1
module RSpec
-
1
module Mocks
-
# ArgumentMatchers are placeholders that you can include in message
-
# expectations to match arguments against a broader check than simple
-
# equality.
-
#
-
# With the exception of `any_args` and `no_args`, they all match against
-
# the arg in same position in the argument list.
-
#
-
# @see ArgumentListMatcher
-
1
module ArgumentMatchers
-
# Acts like an arg splat, matching any number of args at any point in an arg list.
-
#
-
# @example
-
# expect(object).to receive(:message).with(1, 2, any_args)
-
#
-
# # matches any of these:
-
# object.message(1, 2)
-
# object.message(1, 2, 3)
-
# object.message(1, 2, 3, 4)
-
1
def any_args
-
AnyArgsMatcher::INSTANCE
-
end
-
-
# Matches any argument at all.
-
#
-
# @example
-
# expect(object).to receive(:message).with(anything)
-
1
def anything
-
AnyArgMatcher::INSTANCE
-
end
-
-
# Matches no arguments.
-
#
-
# @example
-
# expect(object).to receive(:message).with(no_args)
-
1
def no_args
-
NoArgsMatcher::INSTANCE
-
end
-
-
# Matches if the actual argument responds to the specified messages.
-
#
-
# @example
-
# expect(object).to receive(:message).with(duck_type(:hello))
-
# expect(object).to receive(:message).with(duck_type(:hello, :goodbye))
-
1
def duck_type(*args)
-
DuckTypeMatcher.new(*args)
-
end
-
-
# Matches a boolean value.
-
#
-
# @example
-
# expect(object).to receive(:message).with(boolean())
-
1
def boolean
-
BooleanMatcher::INSTANCE
-
end
-
-
# Matches a hash that includes the specified key(s) or key/value pairs.
-
# Ignores any additional keys.
-
#
-
# @example
-
# expect(object).to receive(:message).with(hash_including(:key => val))
-
# expect(object).to receive(:message).with(hash_including(:key))
-
# expect(object).to receive(:message).with(hash_including(:key, :key2 => val2))
-
1
def hash_including(*args)
-
HashIncludingMatcher.new(ArgumentMatchers.anythingize_lonely_keys(*args))
-
end
-
-
# Matches an array that includes the specified items at least once.
-
# Ignores duplicates and additional values
-
#
-
# @example
-
# expect(object).to receive(:message).with(array_including(1,2,3))
-
# expect(object).to receive(:message).with(array_including([1,2,3]))
-
1
def array_including(*args)
-
actually_an_array = Array === args.first && args.count == 1 ? args.first : args
-
ArrayIncludingMatcher.new(actually_an_array)
-
end
-
-
# Matches a hash that doesn't include the specified key(s) or key/value.
-
#
-
# @example
-
# expect(object).to receive(:message).with(hash_excluding(:key => val))
-
# expect(object).to receive(:message).with(hash_excluding(:key))
-
# expect(object).to receive(:message).with(hash_excluding(:key, :key2 => :val2))
-
1
def hash_excluding(*args)
-
HashExcludingMatcher.new(ArgumentMatchers.anythingize_lonely_keys(*args))
-
end
-
-
1
alias_method :hash_not_including, :hash_excluding
-
-
# Matches if `arg.instance_of?(klass)`
-
#
-
# @example
-
# expect(object).to receive(:message).with(instance_of(Thing))
-
1
def instance_of(klass)
-
InstanceOf.new(klass)
-
end
-
-
1
alias_method :an_instance_of, :instance_of
-
-
# Matches if `arg.kind_of?(klass)`
-
#
-
# @example
-
# expect(object).to receive(:message).with(kind_of(Thing))
-
1
def kind_of(klass)
-
KindOf.new(klass)
-
end
-
-
1
alias_method :a_kind_of, :kind_of
-
-
# @private
-
1
def self.anythingize_lonely_keys(*args)
-
hash = args.last.class == Hash ? args.delete_at(-1) : {}
-
args.each { | arg | hash[arg] = AnyArgMatcher::INSTANCE }
-
hash
-
end
-
-
# Intended to be subclassed by stateless, immutable argument matchers.
-
# Provides a `<klass name>::INSTANCE` constant for accessing a global
-
# singleton instance of the matcher. There is no need to construct
-
# multiple instance since there is no state. It also facilities the
-
# special case logic we need for some of these matchers, by making it
-
# easy to do comparisons like: `[klass::INSTANCE] == args` rather than
-
# `args.count == 1 && klass === args.first`.
-
#
-
# @private
-
1
class SingletonMatcher
-
1
private_class_method :new
-
-
1
def self.inherited(subklass)
-
4
subklass.const_set(:INSTANCE, subklass.send(:new))
-
end
-
end
-
-
# @private
-
1
class AnyArgsMatcher < SingletonMatcher
-
1
def description
-
"*(any args)"
-
end
-
end
-
-
# @private
-
1
class AnyArgMatcher < SingletonMatcher
-
1
def ===(_other)
-
true
-
end
-
-
1
def description
-
"anything"
-
end
-
end
-
-
# @private
-
1
class NoArgsMatcher < SingletonMatcher
-
1
def description
-
"no args"
-
end
-
end
-
-
# @private
-
1
class BooleanMatcher < SingletonMatcher
-
1
def ===(value)
-
true == value || false == value
-
end
-
-
1
def description
-
"boolean"
-
end
-
end
-
-
# @private
-
1
class BaseHashMatcher
-
1
def initialize(expected)
-
@expected = expected
-
end
-
-
1
def ===(predicate, actual)
-
@expected.__send__(predicate) do |k, v|
-
actual.key?(k) && Support::FuzzyMatcher.values_match?(v, actual[k])
-
end
-
rescue NoMethodError
-
false
-
end
-
-
1
def description(name)
-
"#{name}(#{formatted_expected_hash.inspect.sub(/^\{/, "").sub(/\}$/, "")})"
-
end
-
-
1
private
-
-
1
def formatted_expected_hash
-
Hash[
-
@expected.map do |k, v|
-
k = RSpec::Support.rspec_description_for_object(k)
-
v = RSpec::Support.rspec_description_for_object(v)
-
-
[k, v]
-
end
-
]
-
end
-
end
-
-
# @private
-
1
class HashIncludingMatcher < BaseHashMatcher
-
1
def ===(actual)
-
super(:all?, actual)
-
end
-
-
1
def description
-
super("hash_including")
-
end
-
end
-
-
# @private
-
1
class HashExcludingMatcher < BaseHashMatcher
-
1
def ===(actual)
-
super(:none?, actual)
-
end
-
-
1
def description
-
super("hash_not_including")
-
end
-
end
-
-
# @private
-
1
class ArrayIncludingMatcher
-
1
def initialize(expected)
-
@expected = expected
-
end
-
-
1
def ===(actual)
-
actual = actual.uniq
-
@expected.uniq.all? do |expected_element|
-
actual.any? do |actual_element|
-
RSpec::Support::FuzzyMatcher.values_match?(expected_element, actual_element)
-
end
-
end
-
end
-
-
1
def description
-
"array_including(#{formatted_expected_values})"
-
end
-
-
1
private
-
-
1
def formatted_expected_values
-
@expected.map do |x|
-
RSpec::Support.rspec_description_for_object(x)
-
end.join(", ")
-
end
-
end
-
-
# @private
-
1
class DuckTypeMatcher
-
1
def initialize(*methods_to_respond_to)
-
@methods_to_respond_to = methods_to_respond_to
-
end
-
-
1
def ===(value)
-
@methods_to_respond_to.all? { |message| value.respond_to?(message) }
-
end
-
-
1
def description
-
"duck_type(#{@methods_to_respond_to.map(&:inspect).join(', ')})"
-
end
-
end
-
-
# @private
-
1
class InstanceOf
-
1
def initialize(klass)
-
@klass = klass
-
end
-
-
1
def ===(actual)
-
actual.instance_of?(@klass)
-
end
-
-
1
def description
-
"an_instance_of(#{@klass.name})"
-
end
-
end
-
-
# @private
-
1
class KindOf
-
1
def initialize(klass)
-
@klass = klass
-
end
-
-
1
def ===(actual)
-
actual.kind_of?(@klass)
-
end
-
-
1
def description
-
"kind of #{@klass.name}"
-
end
-
end
-
-
1
matcher_namespace = name + '::'
-
1
::RSpec::Support.register_matcher_definition do |object|
-
# This is the best we have for now. We should tag all of our matchers
-
# with a module or something so we can test for it directly.
-
#
-
# (Note Module#parent in ActiveSupport is defined in a similar way.)
-
begin
-
object.class.name.include?(matcher_namespace)
-
rescue NoMethodError
-
# Some objects, like BasicObject, don't implemented standard
-
# reflection methods.
-
false
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# Provides configuration options for rspec-mocks.
-
1
class Configuration
-
1
def initialize
-
1
@allow_message_expectations_on_nil = nil
-
1
@yield_receiver_to_any_instance_implementation_blocks = true
-
1
@verify_doubled_constant_names = false
-
1
@transfer_nested_constants = false
-
1
@verify_partial_doubles = false
-
end
-
-
# Sets whether RSpec will warn, ignore, or fail a test when
-
# expectations are set on nil.
-
# By default, when this flag is not set, warning messages are issued when
-
# expectations are set on nil. This is to prevent false-positives and to
-
# catch potential bugs early on.
-
# When set to `true`, warning messages are suppressed.
-
# When set to `false`, it will raise an error.
-
#
-
# @example
-
# RSpec.configure do |config|
-
# config.mock_with :rspec do |mocks|
-
# mocks.allow_message_expectations_on_nil = false
-
# end
-
# end
-
1
attr_accessor :allow_message_expectations_on_nil
-
-
1
def yield_receiver_to_any_instance_implementation_blocks?
-
@yield_receiver_to_any_instance_implementation_blocks
-
end
-
-
# Sets whether or not RSpec will yield the receiving instance of a
-
# message to blocks that are used for any_instance stub implementations.
-
# When set, the first yielded argument will be the receiving instance.
-
# Defaults to `true`.
-
#
-
# @example
-
# RSpec.configure do |rspec|
-
# rspec.mock_with :rspec do |mocks|
-
# mocks.yield_receiver_to_any_instance_implementation_blocks = false
-
# end
-
# end
-
1
attr_writer :yield_receiver_to_any_instance_implementation_blocks
-
-
# Adds `stub` and `should_receive` to the given
-
# modules or classes. This is usually only necessary
-
# if you application uses some proxy classes that
-
# "strip themselves down" to a bare minimum set of
-
# methods and remove `stub` and `should_receive` in
-
# the process.
-
#
-
# @example
-
# RSpec.configure do |rspec|
-
# rspec.mock_with :rspec do |mocks|
-
# mocks.add_stub_and_should_receive_to Delegator
-
# end
-
# end
-
#
-
1
def add_stub_and_should_receive_to(*modules)
-
modules.each do |mod|
-
Syntax.enable_should(mod)
-
end
-
end
-
-
# Provides the ability to set either `expect`,
-
# `should` or both syntaxes. RSpec uses `expect`
-
# syntax by default. This is needed if you want to
-
# explicitly enable `should` syntax and/or explicitly
-
# disable `expect` syntax.
-
#
-
# @example
-
# RSpec.configure do |rspec|
-
# rspec.mock_with :rspec do |mocks|
-
# mocks.syntax = [:expect, :should]
-
# end
-
# end
-
#
-
1
def syntax=(*values)
-
1
syntaxes = values.flatten
-
1
if syntaxes.include?(:expect)
-
1
Syntax.enable_expect
-
else
-
Syntax.disable_expect
-
end
-
-
1
if syntaxes.include?(:should)
-
1
Syntax.enable_should
-
else
-
Syntax.disable_should
-
end
-
end
-
-
# Returns an array with a list of syntaxes
-
# that are enabled.
-
#
-
# @example
-
# unless RSpec::Mocks.configuration.syntax.include?(:expect)
-
# raise "this RSpec extension gem requires the rspec-mocks `:expect` syntax"
-
# end
-
#
-
1
def syntax
-
syntaxes = []
-
syntaxes << :should if Syntax.should_enabled?
-
syntaxes << :expect if Syntax.expect_enabled?
-
syntaxes
-
end
-
-
1
def verify_doubled_constant_names?
-
!!@verify_doubled_constant_names
-
end
-
-
# When this is set to true, an error will be raised when
-
# `instance_double` or `class_double` is given the name of an undefined
-
# constant. You probably only want to set this when running your entire
-
# test suite, with all production code loaded. Setting this for an
-
# isolated unit test will prevent you from being able to isolate it!
-
1
attr_writer :verify_doubled_constant_names
-
-
# Provides a way to perform customisations when verifying doubles.
-
#
-
# @example
-
# RSpec::Mocks.configuration.before_verifying_doubles do |ref|
-
# ref.some_method!
-
# end
-
1
def before_verifying_doubles(&block)
-
1
verifying_double_callbacks << block
-
end
-
1
alias :when_declaring_verifying_double :before_verifying_doubles
-
-
# @api private
-
# Returns an array of blocks to call when verifying doubles
-
1
def verifying_double_callbacks
-
1
@verifying_double_callbacks ||= []
-
end
-
-
1
def transfer_nested_constants?
-
!!@transfer_nested_constants
-
end
-
-
# Sets the default for the `transfer_nested_constants` option when
-
# stubbing constants.
-
1
attr_writer :transfer_nested_constants
-
-
# When set to true, partial mocks will be verified the same as object
-
# doubles. Any stubs will have their arguments checked against the original
-
# method, and methods that do not exist cannot be stubbed.
-
1
def verify_partial_doubles=(val)
-
@verify_partial_doubles = !!val
-
end
-
-
1
def verify_partial_doubles?
-
@verify_partial_doubles
-
end
-
-
1
if ::RSpec.respond_to?(:configuration)
-
1
def color?
-
::RSpec.configuration.color_enabled?
-
end
-
else
-
# Indicates whether or not diffs should be colored.
-
# Delegates to rspec-core's color option if rspec-core
-
# is loaded; otherwise you can set it here.
-
attr_writer :color
-
-
# Indicates whether or not diffs should be colored.
-
# Delegates to rspec-core's color option if rspec-core
-
# is loaded; otherwise you can set it here.
-
def color?
-
@color
-
end
-
end
-
-
# Monkey-patch `Marshal.dump` to enable dumping of mocked or stubbed
-
# objects. By default this will not work since RSpec mocks works by
-
# adding singleton methods that cannot be serialized. This patch removes
-
# these singleton methods before serialization. Setting to falsey removes
-
# the patch.
-
#
-
# This method is idempotent.
-
1
def patch_marshal_to_support_partial_doubles=(val)
-
if val
-
RSpec::Mocks::MarshalExtension.patch!
-
else
-
RSpec::Mocks::MarshalExtension.unpatch!
-
end
-
end
-
-
# @api private
-
# Resets the configured syntax to the default.
-
1
def reset_syntaxes_to_default
-
1
self.syntax = [:should, :expect]
-
1
RSpec::Mocks::Syntax.warn_about_should!
-
end
-
end
-
-
# Mocks specific configuration, as distinct from `RSpec.configuration`
-
# which is core RSpec configuration.
-
1
def self.configuration
-
2
@configuration ||= Configuration.new
-
end
-
-
1
configuration.reset_syntaxes_to_default
-
end
-
end
-
1
RSpec::Support.require_rspec_support "object_formatter"
-
-
1
module RSpec
-
1
module Mocks
-
# Raised when a message expectation is not satisfied.
-
1
MockExpectationError = Class.new(Exception)
-
-
# Raised when a test double is used after it has been torn
-
# down (typically at the end of an rspec-core example).
-
1
ExpiredTestDoubleError = Class.new(MockExpectationError)
-
-
# Raised when doubles or partial doubles are used outside of the per-test lifecycle.
-
1
OutsideOfExampleError = Class.new(StandardError)
-
-
# Raised when an expectation customization method (e.g. `with`,
-
# `and_return`) is called on a message expectation which has already been
-
# invoked.
-
1
MockExpectationAlreadyInvokedError = Class.new(Exception)
-
-
# Raised for situations that RSpec cannot support due to mutations made
-
# externally on arguments that RSpec is holding onto to use for later
-
# comparisons.
-
#
-
# @deprecated We no longer raise this error but the constant remains until
-
# RSpec 4 for SemVer reasons.
-
1
CannotSupportArgMutationsError = Class.new(StandardError)
-
-
# @private
-
1
UnsupportedMatcherError = Class.new(StandardError)
-
# @private
-
1
NegationUnsupportedError = Class.new(StandardError)
-
# @private
-
1
VerifyingDoubleNotDefinedError = Class.new(StandardError)
-
-
# @private
-
1
class ErrorGenerator
-
1
attr_writer :opts
-
-
1
def initialize(target=nil)
-
@target = target
-
end
-
-
# @private
-
1
def opts
-
@opts ||= {}
-
end
-
-
# @private
-
1
def raise_unexpected_message_error(message, args)
-
__raise "#{intro} received unexpected message :#{message} with #{format_args(args)}"
-
end
-
-
# @private
-
1
def raise_unexpected_message_args_error(expectation, args_for_multiple_calls, source_id=nil)
-
__raise error_message(expectation, args_for_multiple_calls), nil, source_id
-
end
-
-
# @private
-
1
def raise_missing_default_stub_error(expectation, args_for_multiple_calls)
-
message = error_message(expectation, args_for_multiple_calls)
-
message << "\n Please stub a default value first if message might be received with other args as well. \n"
-
-
__raise message
-
end
-
-
# @private
-
1
def raise_similar_message_args_error(expectation, args_for_multiple_calls, backtrace_line=nil)
-
__raise error_message(expectation, args_for_multiple_calls), backtrace_line
-
end
-
-
1
def default_error_message(expectation, expected_args, actual_args)
-
"#{intro} received #{expectation.message.inspect} #{unexpected_arguments_message(expected_args, actual_args)}"
-
end
-
-
# rubocop:disable Style/ParameterLists
-
# @private
-
1
def raise_expectation_error(message, expected_received_count, argument_list_matcher,
-
actual_received_count, expectation_count_type, args,
-
backtrace_line=nil, source_id=nil)
-
expected_part = expected_part_of_expectation_error(expected_received_count, expectation_count_type, argument_list_matcher)
-
received_part = received_part_of_expectation_error(actual_received_count, args)
-
__raise "(#{intro(:unwrapped)}).#{message}#{format_args(args)}\n #{expected_part}\n #{received_part}", backtrace_line, source_id
-
end
-
# rubocop:enable Style/ParameterLists
-
-
# @private
-
1
def raise_unimplemented_error(doubled_module, method_name, object)
-
message = case object
-
when InstanceVerifyingDouble
-
"the %s class does not implement the instance method: %s" <<
-
if ObjectMethodReference.for(doubled_module, method_name).implemented?
-
". Perhaps you meant to use `class_double` instead?"
-
else
-
""
-
end
-
when ClassVerifyingDouble
-
"the %s class does not implement the class method: %s" <<
-
if InstanceMethodReference.for(doubled_module, method_name).implemented?
-
". Perhaps you meant to use `instance_double` instead?"
-
else
-
""
-
end
-
else
-
"%s does not implement: %s"
-
end
-
-
__raise message % [doubled_module.description, method_name]
-
end
-
-
# @private
-
1
def raise_non_public_error(method_name, visibility)
-
raise NoMethodError, "%s method `%s' called on %s" % [
-
visibility, method_name, intro
-
]
-
end
-
-
# @private
-
1
def raise_invalid_arguments_error(verifier)
-
__raise verifier.error_message
-
end
-
-
# @private
-
1
def raise_expired_test_double_error
-
raise ExpiredTestDoubleError,
-
"#{intro} was originally created in one example but has leaked into " \
-
"another example and can no longer be used. rspec-mocks' doubles are " \
-
"designed to only last for one example, and you need to create a new " \
-
"one in each example you wish to use it for."
-
end
-
-
# @private
-
1
def describe_expectation(verb, message, expected_received_count, _actual_received_count, args)
-
"#{verb} #{message}#{format_args(args)} #{count_message(expected_received_count)}"
-
end
-
-
# @private
-
1
def raise_out_of_order_error(message)
-
__raise "#{intro} received :#{message} out of order"
-
end
-
-
# @private
-
1
def raise_missing_block_error(args_to_yield)
-
__raise "#{intro} asked to yield |#{arg_list(args_to_yield)}| but no block was passed"
-
end
-
-
# @private
-
1
def raise_wrong_arity_error(args_to_yield, signature)
-
__raise "#{intro} yielded |#{arg_list(args_to_yield)}| to block with #{signature.description}"
-
end
-
-
# @private
-
1
def raise_only_valid_on_a_partial_double(method)
-
__raise "#{intro} is a pure test double. `#{method}` is only " \
-
"available on a partial double."
-
end
-
-
# @private
-
1
def raise_expectation_on_unstubbed_method(method)
-
__raise "#{intro} expected to have received #{method}, but that " \
-
"object is not a spy or method has not been stubbed."
-
end
-
-
# @private
-
1
def raise_expectation_on_mocked_method(method)
-
__raise "#{intro} expected to have received #{method}, but that " \
-
"method has been mocked instead of stubbed or spied."
-
end
-
-
# @private
-
1
def raise_double_negation_error(wrapped_expression)
-
__raise "Isn't life confusing enough? You've already set a " \
-
"negative message expectation and now you are trying to " \
-
"negate it again with `never`. What does an expression like " \
-
"`#{wrapped_expression}.not_to receive(:msg).never` even mean?"
-
end
-
-
# @private
-
1
def raise_verifying_double_not_defined_error(ref)
-
notify(VerifyingDoubleNotDefinedError.new(
-
"#{ref.description.inspect} is not a defined constant. " \
-
"Perhaps you misspelt it? " \
-
"Disable check with `verify_doubled_constant_names` configuration option."
-
))
-
end
-
-
# @private
-
1
def raise_have_received_disallowed(type, reason)
-
__raise "Using #{type}(...) with the `have_received` " \
-
"matcher is not supported#{reason}."
-
end
-
-
# @private
-
1
def raise_cant_constrain_count_for_negated_have_received_error(count_constraint)
-
__raise "can't use #{count_constraint} when negative"
-
end
-
-
# @private
-
1
def raise_method_not_stubbed_error(method_name)
-
__raise "The method `#{method_name}` was not stubbed or was already unstubbed"
-
end
-
-
# @private
-
1
def raise_already_invoked_error(message, calling_customization)
-
error_message = "The message expectation for #{intro}.#{message} has already been invoked " \
-
"and cannot be modified further (e.g. using `#{calling_customization}`). All message expectation " \
-
"customizations must be applied before it is used for the first time."
-
-
notify MockExpectationAlreadyInvokedError.new(error_message)
-
end
-
-
1
def raise_expectation_on_nil_error(method_name)
-
__raise expectation_on_nil_message(method_name)
-
end
-
-
1
def expectation_on_nil_message(method_name)
-
"An expectation of `:#{method_name}` was set on `nil`. " \
-
"To allow expectations on `nil` and suppress this message, set `config.allow_expectations_on_nil` to `true`. " \
-
"To disallow expectations on `nil`, set `config.allow_expectations_on_nil` to `false`"
-
end
-
-
1
private
-
-
1
def received_part_of_expectation_error(actual_received_count, args)
-
"received: #{count_message(actual_received_count)}" +
-
method_call_args_description(args) do
-
actual_received_count > 0 && args.length > 0
-
end
-
end
-
-
1
def expected_part_of_expectation_error(expected_received_count, expectation_count_type, argument_list_matcher)
-
"expected: #{count_message(expected_received_count, expectation_count_type)}" +
-
method_call_args_description(argument_list_matcher.expected_args) do
-
argument_list_matcher.expected_args.length > 0
-
end
-
end
-
-
1
def method_call_args_description(args)
-
case args.first
-
when ArgumentMatchers::AnyArgsMatcher then " with any arguments"
-
when ArgumentMatchers::NoArgsMatcher then " with no arguments"
-
else
-
if yield
-
" with arguments: #{format_args(args)}"
-
else
-
""
-
end
-
end
-
end
-
-
1
def unexpected_arguments_message(expected_args_string, actual_args_string)
-
"with unexpected arguments\n expected: #{expected_args_string}\n got: #{actual_args_string}"
-
end
-
-
1
def error_message(expectation, args_for_multiple_calls)
-
expected_args = format_args(expectation.expected_args)
-
actual_args = format_received_args(args_for_multiple_calls)
-
message = default_error_message(expectation, expected_args, actual_args)
-
-
if args_for_multiple_calls.one?
-
diff = diff_message(expectation.expected_args, args_for_multiple_calls.first)
-
message << "\nDiff:#{diff}" unless diff.strip.empty?
-
end
-
-
message
-
end
-
-
1
def diff_message(expected_args, actual_args)
-
formatted_expected_args = expected_args.map do |x|
-
RSpec::Support.rspec_description_for_object(x)
-
end
-
-
formatted_expected_args, actual_args = unpack_string_args(formatted_expected_args, actual_args)
-
-
differ.diff(actual_args, formatted_expected_args)
-
end
-
-
1
def unpack_string_args(formatted_expected_args, actual_args)
-
if [formatted_expected_args, actual_args].all? { |x| list_of_exactly_one_string?(x) }
-
[formatted_expected_args.first, actual_args.first]
-
else
-
[formatted_expected_args, actual_args]
-
end
-
end
-
-
1
def list_of_exactly_one_string?(args)
-
Array === args && args.count == 1 && String === args.first
-
end
-
-
1
def differ
-
RSpec::Support::Differ.new(:color => RSpec::Mocks.configuration.color?)
-
end
-
-
1
def intro(unwrapped=false)
-
case @target
-
when TestDouble then TestDoubleFormatter.format(@target, unwrapped)
-
when Class then
-
formatted = "#{@target.inspect} (class)"
-
return formatted if unwrapped
-
"#<#{formatted}>"
-
when NilClass then "nil"
-
else @target
-
end
-
end
-
-
1
def __raise(message, backtrace_line=nil, source_id=nil)
-
message = opts[:message] unless opts[:message].nil?
-
exception = RSpec::Mocks::MockExpectationError.new(message)
-
prepend_to_backtrace(exception, backtrace_line) if backtrace_line
-
notify exception, :source_id => source_id
-
end
-
-
1
if RSpec::Support::Ruby.jruby?
-
def prepend_to_backtrace(exception, line)
-
raise exception
-
rescue RSpec::Mocks::MockExpectationError => with_backtrace
-
with_backtrace.backtrace.unshift(line)
-
end
-
else
-
1
def prepend_to_backtrace(exception, line)
-
exception.set_backtrace(caller.unshift line)
-
end
-
end
-
-
1
def notify(*args)
-
RSpec::Support.notify_failure(*args)
-
end
-
-
1
def format_args(args)
-
return "(no args)" if args.empty?
-
"(#{arg_list(args)})"
-
end
-
-
1
def arg_list(args)
-
args.map { |arg| RSpec::Support::ObjectFormatter.format(arg) }.join(", ")
-
end
-
-
1
def format_received_args(args_for_multiple_calls)
-
grouped_args(args_for_multiple_calls).map do |args_for_one_call, index|
-
"#{format_args(args_for_one_call)}#{group_count(index, args_for_multiple_calls)}"
-
end.join("\n ")
-
end
-
-
1
def count_message(count, expectation_count_type=nil)
-
return "at least #{times(count.abs)}" if count < 0 || expectation_count_type == :at_least
-
return "at most #{times(count)}" if expectation_count_type == :at_most
-
times(count)
-
end
-
-
1
def times(count)
-
"#{count} time#{count == 1 ? '' : 's'}"
-
end
-
-
1
def grouped_args(args)
-
Hash[args.group_by { |x| x }.map { |k, v| [k, v.count] }]
-
end
-
-
1
def group_count(index, args)
-
" (#{times(index)})" if args.size > 1 || index > 1
-
end
-
end
-
-
# @private
-
1
def self.error_generator
-
@error_generator ||= ErrorGenerator.new
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_mocks 'object_reference'
-
-
1
module RSpec
-
1
module Mocks
-
# Contains methods intended to be used from within code examples.
-
# Mix this in to your test context (such as a test framework base class)
-
# to use rspec-mocks with your test framework. If you're using rspec-core,
-
# it'll take care of doing this for you.
-
1
module ExampleMethods
-
1
include RSpec::Mocks::ArgumentMatchers
-
-
# @overload double()
-
# @overload double(name)
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @overload double(stubs)
-
# @param stubs (Hash) hash of message/return-value pairs
-
# @overload double(name, stubs)
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @param stubs (Hash) hash of message/return-value pairs
-
# @return (Double)
-
#
-
# Constructs an instance of [RSpec::Mocks::Double](RSpec::Mocks::Double) configured
-
# with an optional name, used for reporting in failure messages, and an optional
-
# hash of message/return-value pairs.
-
#
-
# @example
-
# book = double("book", :title => "The RSpec Book")
-
# book.title #=> "The RSpec Book"
-
#
-
# card = double("card", :suit => "Spades", :rank => "A")
-
# card.suit #=> "Spades"
-
# card.rank #=> "A"
-
#
-
1
def double(*args)
-
ExampleMethods.declare_double(Double, *args)
-
end
-
-
# @overload instance_double(doubled_class)
-
# @param doubled_class [String, Class]
-
# @overload instance_double(doubled_class, name)
-
# @param doubled_class [String, Class]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @overload instance_double(doubled_class, stubs)
-
# @param doubled_class [String, Class]
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @overload instance_double(doubled_class, name, stubs)
-
# @param doubled_class [String, Class]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @return InstanceVerifyingDouble
-
#
-
# Constructs a test double against a specific class. If the given class
-
# name has been loaded, only instance methods defined on the class are
-
# allowed to be stubbed. In all other ways it behaves like a
-
# [double](double).
-
1
def instance_double(doubled_class, *args)
-
ref = ObjectReference.for(doubled_class)
-
ExampleMethods.declare_verifying_double(InstanceVerifyingDouble, ref, *args)
-
end
-
-
# @overload class_double(doubled_class)
-
# @param doubled_class [String, Module]
-
# @overload class_double(doubled_class, name)
-
# @param doubled_class [String, Module]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @overload class_double(doubled_class, stubs)
-
# @param doubled_class [String, Module]
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @overload class_double(doubled_class, name, stubs)
-
# @param doubled_class [String, Module]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @return ClassVerifyingDouble
-
#
-
# Constructs a test double against a specific class. If the given class
-
# name has been loaded, only class methods defined on the class are
-
# allowed to be stubbed. In all other ways it behaves like a
-
# [double](double).
-
1
def class_double(doubled_class, *args)
-
ref = ObjectReference.for(doubled_class)
-
ExampleMethods.declare_verifying_double(ClassVerifyingDouble, ref, *args)
-
end
-
-
# @overload object_double(object_or_name)
-
# @param object_or_name [String, Object]
-
# @overload object_double(object_or_name, name)
-
# @param object_or_name [String, Object]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @overload object_double(object_or_name, stubs)
-
# @param object_or_name [String, Object]
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @overload object_double(object_or_name, name, stubs)
-
# @param object_or_name [String, Object]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @return ObjectVerifyingDouble
-
#
-
# Constructs a test double against a specific object. Only the methods
-
# the object responds to are allowed to be stubbed. If a String argument
-
# is provided, it is assumed to reference a constant object which is used
-
# for verification. In all other ways it behaves like a [double](double).
-
1
def object_double(object_or_name, *args)
-
ref = ObjectReference.for(object_or_name, :allow_direct_object_refs)
-
ExampleMethods.declare_verifying_double(ObjectVerifyingDouble, ref, *args)
-
end
-
-
# @overload spy()
-
# @overload spy(name)
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @overload spy(stubs)
-
# @param stubs (Hash) hash of message/return-value pairs
-
# @overload spy(name, stubs)
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @param stubs (Hash) hash of message/return-value pairs
-
# @return (Double)
-
#
-
# Constructs a test double that is optimized for use with
-
# `have_received`. With a normal double one has to stub methods in order
-
# to be able to spy them. A spy automatically spies on all methods.
-
1
def spy(*args)
-
double(*args).as_null_object
-
end
-
-
# @overload instance_spy(doubled_class)
-
# @param doubled_class [String, Class]
-
# @overload instance_spy(doubled_class, name)
-
# @param doubled_class [String, Class]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @overload instance_spy(doubled_class, stubs)
-
# @param doubled_class [String, Class]
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @overload instance_spy(doubled_class, name, stubs)
-
# @param doubled_class [String, Class]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @return InstanceVerifyingDouble
-
#
-
# Constructs a test double that is optimized for use with `have_received`
-
# against a specific class. If the given class name has been loaded, only
-
# instance methods defined on the class are allowed to be stubbed. With
-
# a normal double one has to stub methods in order to be able to spy
-
# them. An instance_spy automatically spies on all instance methods to
-
# which the class responds.
-
1
def instance_spy(*args)
-
instance_double(*args).as_null_object
-
end
-
-
# @overload object_spy(object_or_name)
-
# @param object_or_name [String, Object]
-
# @overload object_spy(object_or_name, name)
-
# @param object_or_name [String, Class]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @overload object_spy(object_or_name, stubs)
-
# @param object_or_name [String, Object]
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @overload object_spy(object_or_name, name, stubs)
-
# @param object_or_name [String, Class]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @return ObjectVerifyingDouble
-
#
-
# Constructs a test double that is optimized for use with `have_received`
-
# against a specific object. Only instance methods defined on the object
-
# are allowed to be stubbed. With a normal double one has to stub
-
# methods in order to be able to spy them. An object_spy automatically
-
# spies on all methods to which the object responds.
-
1
def object_spy(*args)
-
object_double(*args).as_null_object
-
end
-
-
# @overload class_spy(doubled_class)
-
# @param doubled_class [String, Module]
-
# @overload class_spy(doubled_class, name)
-
# @param doubled_class [String, Class]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @overload class_spy(doubled_class, stubs)
-
# @param doubled_class [String, Module]
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @overload class_spy(doubled_class, name, stubs)
-
# @param doubled_class [String, Class]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @return ClassVerifyingDouble
-
#
-
# Constructs a test double that is optimized for use with `have_received`
-
# against a specific class. If the given class name has been loaded,
-
# only class methods defined on the class are allowed to be stubbed.
-
# With a normal double one has to stub methods in order to be able to spy
-
# them. An class_spy automatically spies on all class methods to which the
-
# class responds.
-
1
def class_spy(*args)
-
class_double(*args).as_null_object
-
end
-
-
# Disables warning messages about expectations being set on nil.
-
#
-
# By default warning messages are issued when expectations are set on
-
# nil. This is to prevent false-positives and to catch potential bugs
-
# early on.
-
# @deprecated Use {RSpec::Mocks::Configuration#allow_message_expectations_on_nil} instead.
-
1
def allow_message_expectations_on_nil
-
RSpec::Mocks.space.proxy_for(nil).warn_about_expectations = false
-
end
-
-
# Stubs the named constant with the given value.
-
# Like method stubs, the constant will be restored
-
# to its original value (or lack of one, if it was
-
# undefined) when the example completes.
-
#
-
# @param constant_name [String] The fully qualified name of the constant. The current
-
# constant scoping at the point of call is not considered.
-
# @param value [Object] The value to make the constant refer to. When the
-
# example completes, the constant will be restored to its prior state.
-
# @param options [Hash] Stubbing options.
-
# @option options :transfer_nested_constants [Boolean, Array<Symbol>] Determines
-
# what nested constants, if any, will be transferred from the original value
-
# of the constant to the new value of the constant. This only works if both
-
# the original and new values are modules (or classes).
-
# @return [Object] the stubbed value of the constant
-
#
-
# @example
-
# stub_const("MyClass", Class.new) # => Replaces (or defines) MyClass with a new class object.
-
# stub_const("SomeModel::PER_PAGE", 5) # => Sets SomeModel::PER_PAGE to 5.
-
#
-
# class CardDeck
-
# SUITS = [:Spades, :Diamonds, :Clubs, :Hearts]
-
# NUM_CARDS = 52
-
# end
-
#
-
# stub_const("CardDeck", Class.new)
-
# CardDeck::SUITS # => uninitialized constant error
-
# CardDeck::NUM_CARDS # => uninitialized constant error
-
#
-
# stub_const("CardDeck", Class.new, :transfer_nested_constants => true)
-
# CardDeck::SUITS # => our suits array
-
# CardDeck::NUM_CARDS # => 52
-
#
-
# stub_const("CardDeck", Class.new, :transfer_nested_constants => [:SUITS])
-
# CardDeck::SUITS # => our suits array
-
# CardDeck::NUM_CARDS # => uninitialized constant error
-
1
def stub_const(constant_name, value, options={})
-
ConstantMutator.stub(constant_name, value, options)
-
end
-
-
# Hides the named constant with the given value. The constant will be
-
# undefined for the duration of the test.
-
#
-
# Like method stubs, the constant will be restored to its original value
-
# when the example completes.
-
#
-
# @param constant_name [String] The fully qualified name of the constant.
-
# The current constant scoping at the point of call is not considered.
-
#
-
# @example
-
# hide_const("MyClass") # => MyClass is now an undefined constant
-
1
def hide_const(constant_name)
-
ConstantMutator.hide(constant_name)
-
end
-
-
# Verifies that the given object received the expected message during the
-
# course of the test. On a spy objects or as null object doubles this
-
# works for any method, on other objects the method must have
-
# been stubbed beforehand in order for messages to be verified.
-
#
-
# Stubbing and verifying messages received in this way implements the
-
# Test Spy pattern.
-
#
-
# @param method_name [Symbol] name of the method expected to have been
-
# called.
-
#
-
# @example
-
# invitation = double('invitation', accept: true)
-
# user.accept_invitation(invitation)
-
# expect(invitation).to have_received(:accept)
-
#
-
# # You can also use most message expectations:
-
# expect(invitation).to have_received(:accept).with(mailer).once
-
#
-
# @note `have_received(...).with(...)` is unable to work properly when
-
# passed arguments are mutated after the spy records the received message.
-
1
def have_received(method_name, &block)
-
Matchers::HaveReceived.new(method_name, &block)
-
end
-
-
# @method expect
-
# Used to wrap an object in preparation for setting a mock expectation
-
# on it.
-
#
-
# @example
-
# expect(obj).to receive(:foo).with(5).and_return(:return_value)
-
#
-
# @note This method is usually provided by rspec-expectations. However,
-
# if you use rspec-mocks without rspec-expectations, there's a definition
-
# of it that is made available here. If you disable the `:expect` syntax
-
# this method will be undefined.
-
-
# @method allow
-
# Used to wrap an object in preparation for stubbing a method
-
# on it.
-
#
-
# @example
-
# allow(dbl).to receive(:foo).with(5).and_return(:return_value)
-
#
-
# @note If you disable the `:expect` syntax this method will be undefined.
-
-
# @method expect_any_instance_of
-
# Used to wrap a class in preparation for setting a mock expectation
-
# on instances of it.
-
#
-
# @example
-
# expect_any_instance_of(MyClass).to receive(:foo)
-
#
-
# @note If you disable the `:expect` syntax this method will be undefined.
-
-
# @method allow_any_instance_of
-
# Used to wrap a class in preparation for stubbing a method
-
# on instances of it.
-
#
-
# @example
-
# allow_any_instance_of(MyClass).to receive(:foo)
-
#
-
# @note This is only available when you have enabled the `expect` syntax.
-
-
# @method receive
-
# Used to specify a message that you expect or allow an object
-
# to receive. The object returned by `receive` supports the same
-
# fluent interface that `should_receive` and `stub` have always
-
# supported, allowing you to constrain the arguments or number of
-
# times, and configure how the object should respond to the message.
-
#
-
# @example
-
# expect(obj).to receive(:hello).with("world").exactly(3).times
-
#
-
# @note If you disable the `:expect` syntax this method will be undefined.
-
-
# @method receive_messages
-
# Shorthand syntax used to setup message(s), and their return value(s),
-
# that you expect or allow an object to receive. The method takes a hash
-
# of messages and their respective return values. Unlike with `receive`,
-
# you cannot apply further customizations using a block or the fluent
-
# interface.
-
#
-
# @example
-
# allow(obj).to receive_messages(:speak => "Hello World")
-
# allow(obj).to receive_messages(:speak => "Hello", :meow => "Meow")
-
#
-
# @note If you disable the `:expect` syntax this method will be undefined.
-
-
# @method receive_message_chain
-
# @overload receive_message_chain(method1, method2)
-
# @overload receive_message_chain("method1.method2")
-
# @overload receive_message_chain(method1, method_to_value_hash)
-
#
-
# stubs/mocks a chain of messages on an object or test double.
-
#
-
# ## Warning:
-
#
-
# Chains can be arbitrarily long, which makes it quite painless to
-
# violate the Law of Demeter in violent ways, so you should consider any
-
# use of `receive_message_chain` a code smell. Even though not all code smells
-
# indicate real problems (think fluent interfaces), `receive_message_chain` still
-
# results in brittle examples. For example, if you write
-
# `allow(foo).to receive_message_chain(:bar, :baz => 37)` in a spec and then the
-
# implementation calls `foo.baz.bar`, the stub will not work.
-
#
-
# @example
-
# allow(double).to receive_message_chain("foo.bar") { :baz }
-
# allow(double).to receive_message_chain(:foo, :bar => :baz)
-
# allow(double).to receive_message_chain(:foo, :bar) { :baz }
-
#
-
# # Given any of ^^ these three forms ^^:
-
# double.foo.bar # => :baz
-
#
-
# # Common use in Rails/ActiveRecord:
-
# allow(Article).to receive_message_chain("recent.published") { [Article.new] }
-
#
-
# @note If you disable the `:expect` syntax this method will be undefined.
-
-
# @private
-
1
def self.included(klass)
-
1
klass.class_exec do
-
# This gets mixed in so that if `RSpec::Matchers` is included in
-
# `klass` later, it's definition of `expect` will take precedence.
-
1
include ExpectHost unless method_defined?(:expect)
-
end
-
end
-
-
# @private
-
1
def self.extended(object)
-
# This gets extended in so that if `RSpec::Matchers` is included in
-
# `klass` later, it's definition of `expect` will take precedence.
-
object.extend ExpectHost unless object.respond_to?(:expect)
-
end
-
-
# @private
-
1
def self.declare_verifying_double(type, ref, *args)
-
if RSpec::Mocks.configuration.verify_doubled_constant_names? &&
-
!ref.defined?
-
-
RSpec::Mocks.error_generator.raise_verifying_double_not_defined_error(ref)
-
end
-
-
RSpec::Mocks.configuration.verifying_double_callbacks.each do |block|
-
block.call(ref)
-
end
-
-
declare_double(type, ref, *args)
-
end
-
-
# @private
-
1
def self.declare_double(type, *args)
-
args << {} unless Hash === args.last
-
type.new(*args)
-
end
-
-
# This module exists to host the `expect` method for cases where
-
# rspec-mocks is used w/o rspec-expectations.
-
1
module ExpectHost
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
class InstanceMethodStasher
-
1
def initialize(object, method)
-
@object = object
-
@method = method
-
@klass = (class << object; self; end)
-
-
@original_method = nil
-
@method_is_stashed = false
-
end
-
-
1
attr_reader :original_method
-
-
1
if RUBY_VERSION.to_f < 1.9
-
# @private
-
def method_is_stashed?
-
@method_is_stashed
-
end
-
-
# @private
-
def stash
-
return if !method_defined_directly_on_klass? || @method_is_stashed
-
-
@klass.__send__(:alias_method, stashed_method_name, @method)
-
@method_is_stashed = true
-
end
-
-
# @private
-
def stashed_method_name
-
"obfuscated_by_rspec_mocks__#{@method}"
-
end
-
-
# @private
-
def restore
-
return unless @method_is_stashed
-
-
if @klass.__send__(:method_defined?, @method)
-
@klass.__send__(:undef_method, @method)
-
end
-
@klass.__send__(:alias_method, @method, stashed_method_name)
-
@klass.__send__(:remove_method, stashed_method_name)
-
@method_is_stashed = false
-
end
-
else
-
-
# @private
-
1
def method_is_stashed?
-
!!@original_method
-
end
-
-
# @private
-
1
def stash
-
return unless method_defined_directly_on_klass?
-
@original_method ||= ::RSpec::Support.method_handle_for(@object, @method)
-
@klass.__send__(:undef_method, @method)
-
end
-
-
# @private
-
1
def restore
-
return unless @original_method
-
-
if @klass.__send__(:method_defined?, @method)
-
@klass.__send__(:undef_method, @method)
-
end
-
-
handle_restoration_failures do
-
@klass.__send__(:define_method, @method, @original_method)
-
end
-
-
@original_method = nil
-
end
-
end
-
-
1
if RUBY_DESCRIPTION.include?('2.0.0p247') || RUBY_DESCRIPTION.include?('2.0.0p195')
-
# ruby 2.0.0-p247 and 2.0.0-p195 both have a bug that we can't work around :(.
-
# https://bugs.ruby-lang.org/issues/8686
-
def handle_restoration_failures
-
yield
-
rescue TypeError
-
RSpec.warn_with(
-
"RSpec failed to properly restore a partial double (#{@object.inspect}) " \
-
"to its original state due to a known bug in MRI 2.0.0-p195 & p247 " \
-
"(https://bugs.ruby-lang.org/issues/8686). This object may remain " \
-
"screwed up for the rest of this process. Please upgrade to 2.0.0-p353 or above.",
-
:call_site => nil, :use_spec_location_as_call_site => true
-
)
-
end
-
else
-
1
def handle_restoration_failures
-
# No known reasons for restoration to fail on other rubies.
-
yield
-
end
-
end
-
-
1
private
-
-
# @private
-
1
def method_defined_directly_on_klass?
-
method_defined_on_klass? && method_owned_by_klass?
-
end
-
-
# @private
-
1
def method_defined_on_klass?(klass=@klass)
-
MethodReference.method_defined_at_any_visibility?(klass, @method)
-
end
-
-
1
def method_owned_by_klass?
-
owner = @klass.instance_method(@method).owner
-
-
# On Ruby 2.0.0+ the owner of a method on a class which has been
-
# `prepend`ed may actually be an instance, e.g.
-
# `#<MyClass:0x007fbb94e3cd10>`, rather than the expected `MyClass`.
-
owner = owner.class unless Module === owner
-
-
# On some 1.9s (e.g. rubinius) aliased methods
-
# can report the wrong owner. Example:
-
# class MyClass
-
# class << self
-
# alias alternate_new new
-
# end
-
# end
-
#
-
# MyClass.owner(:alternate_new) returns `Class` when incorrect,
-
# but we need to consider the owner to be `MyClass` because
-
# it is not actually available on `Class` but is on `MyClass`.
-
# Hence, we verify that the owner actually has the method defined.
-
# If the given owner does not have the method defined, we assume
-
# that the method is actually owned by @klass.
-
owner == @klass || !(method_defined_on_klass?(owner))
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# A message expectation that only allows concrete return values to be set
-
# for a message. While this same effect can be achieved using a standard
-
# MessageExpectation, this version is much faster and so can be used as an
-
# optimization.
-
#
-
# @private
-
1
class SimpleMessageExpectation
-
1
def initialize(message, response, error_generator, backtrace_line=nil)
-
@message, @response, @error_generator, @backtrace_line = message.to_sym, response, error_generator, backtrace_line
-
@received = false
-
end
-
-
1
def invoke(*_)
-
@received = true
-
@response
-
end
-
-
1
def matches?(message, *_)
-
@message == message.to_sym
-
end
-
-
1
def called_max_times?
-
false
-
end
-
-
1
def verify_messages_received
-
return if @received
-
@error_generator.raise_expectation_error(
-
@message, 1, ArgumentListMatcher::MATCH_ALL, 0, nil, [], @backtrace_line
-
)
-
end
-
-
1
def unadvise(_)
-
end
-
end
-
-
# Represents an individual method stub or message expectation. The methods
-
# defined here can be used to configure how it behaves. The methods return
-
# `self` so that they can be chained together to form a fluent interface.
-
1
class MessageExpectation
-
# @!group Configuring Responses
-
-
# @overload and_return(value)
-
# @overload and_return(first_value, second_value)
-
#
-
# Tells the object to return a value when it receives the message. Given
-
# more than one value, the first value is returned the first time the
-
# message is received, the second value is returned the next time, etc,
-
# etc.
-
#
-
# If the message is received more times than there are values, the last
-
# value is received for every subsequent call.
-
#
-
# @return [nil] No further chaining is supported after this.
-
# @example
-
# allow(counter).to receive(:count).and_return(1)
-
# counter.count # => 1
-
# counter.count # => 1
-
#
-
# allow(counter).to receive(:count).and_return(1,2,3)
-
# counter.count # => 1
-
# counter.count # => 2
-
# counter.count # => 3
-
# counter.count # => 3
-
# counter.count # => 3
-
# # etc
-
1
def and_return(first_value, *values)
-
raise_already_invoked_error_if_necessary(__method__)
-
if negative?
-
raise "`and_return` is not supported with negative message expectations"
-
end
-
-
if block_given?
-
raise ArgumentError, "Implementation blocks aren't supported with `and_return`"
-
end
-
-
values.unshift(first_value)
-
@expected_received_count = [@expected_received_count, values.size].max unless ignoring_args? || (@expected_received_count == 0 && @at_least)
-
self.terminal_implementation_action = AndReturnImplementation.new(values)
-
-
nil
-
end
-
-
# Tells the object to delegate to the original unmodified method
-
# when it receives the message.
-
#
-
# @note This is only available on partial doubles.
-
#
-
# @return [nil] No further chaining is supported after this.
-
# @example
-
# expect(counter).to receive(:increment).and_call_original
-
# original_count = counter.count
-
# counter.increment
-
# expect(counter.count).to eq(original_count + 1)
-
1
def and_call_original
-
and_wrap_original do |original, *args, &block|
-
original.call(*args, &block)
-
end
-
end
-
-
# Decorates the stubbed method with the supplied block. The original
-
# unmodified method is passed to the block along with any method call
-
# arguments so you can delegate to it, whilst still being able to
-
# change what args are passed to it and/or change the return value.
-
#
-
# @note This is only available on partial doubles.
-
#
-
# @return [nil] No further chaining is supported after this.
-
# @example
-
# expect(api).to receive(:large_list).and_wrap_original do |original_method, *args, &block|
-
# original_method.call(*args, &block).first(10)
-
# end
-
1
def and_wrap_original(&block)
-
if RSpec::Mocks::TestDouble === @method_double.object
-
@error_generator.raise_only_valid_on_a_partial_double(:and_call_original)
-
else
-
warn_about_stub_override if implementation.inner_action
-
@implementation = AndWrapOriginalImplementation.new(@method_double.original_implementation_callable, block)
-
@yield_receiver_to_implementation_block = false
-
end
-
-
nil
-
end
-
-
# @overload and_raise
-
# @overload and_raise(ExceptionClass)
-
# @overload and_raise(ExceptionClass, message)
-
# @overload and_raise(exception_instance)
-
#
-
# Tells the object to raise an exception when the message is received.
-
#
-
# @return [nil] No further chaining is supported after this.
-
# @note
-
# When you pass an exception class, the MessageExpectation will raise
-
# an instance of it, creating it with `exception` and passing `message`
-
# if specified. If the exception class initializer requires more than
-
# one parameters, you must pass in an instance and not the class,
-
# otherwise this method will raise an ArgumentError exception.
-
#
-
# @example
-
# allow(car).to receive(:go).and_raise
-
# allow(car).to receive(:go).and_raise(OutOfGas)
-
# allow(car).to receive(:go).and_raise(OutOfGas, "At least 2 oz of gas needed to drive")
-
# allow(car).to receive(:go).and_raise(OutOfGas.new(2, :oz))
-
1
def and_raise(*args)
-
raise_already_invoked_error_if_necessary(__method__)
-
self.terminal_implementation_action = Proc.new { raise(*args) }
-
nil
-
end
-
-
# @overload and_throw(symbol)
-
# @overload and_throw(symbol, object)
-
#
-
# Tells the object to throw a symbol (with the object if that form is
-
# used) when the message is received.
-
#
-
# @return [nil] No further chaining is supported after this.
-
# @example
-
# allow(car).to receive(:go).and_throw(:out_of_gas)
-
# allow(car).to receive(:go).and_throw(:out_of_gas, :level => 0.1)
-
1
def and_throw(*args)
-
raise_already_invoked_error_if_necessary(__method__)
-
self.terminal_implementation_action = Proc.new { throw(*args) }
-
nil
-
end
-
-
# Tells the object to yield one or more args to a block when the message
-
# is received.
-
#
-
# @return [MessageExpectation] self, to support further chaining.
-
# @example
-
# stream.stub(:open).and_yield(StringIO.new)
-
1
def and_yield(*args, &block)
-
raise_already_invoked_error_if_necessary(__method__)
-
yield @eval_context = Object.new if block
-
-
# Initialize args to yield now that it's being used, see also: comment
-
# in constructor.
-
@args_to_yield ||= []
-
-
@args_to_yield << args
-
self.initial_implementation_action = AndYieldImplementation.new(@args_to_yield, @eval_context, @error_generator)
-
self
-
end
-
# @!endgroup
-
-
# @!group Constraining Receive Counts
-
-
# Constrain a message expectation to be received a specific number of
-
# times.
-
#
-
# @return [MessageExpectation] self, to support further chaining.
-
# @example
-
# expect(dealer).to receive(:deal_card).exactly(10).times
-
1
def exactly(n, &block)
-
raise_already_invoked_error_if_necessary(__method__)
-
self.inner_implementation_action = block
-
set_expected_received_count :exactly, n
-
self
-
end
-
-
# Constrain a message expectation to be received at least a specific
-
# number of times.
-
#
-
# @return [MessageExpectation] self, to support further chaining.
-
# @example
-
# expect(dealer).to receive(:deal_card).at_least(9).times
-
1
def at_least(n, &block)
-
raise_already_invoked_error_if_necessary(__method__)
-
set_expected_received_count :at_least, n
-
-
if n == 0
-
raise "at_least(0) has been removed, use allow(...).to receive(:message) instead"
-
end
-
-
self.inner_implementation_action = block
-
-
self
-
end
-
-
# Constrain a message expectation to be received at most a specific
-
# number of times.
-
#
-
# @return [MessageExpectation] self, to support further chaining.
-
# @example
-
# expect(dealer).to receive(:deal_card).at_most(10).times
-
1
def at_most(n, &block)
-
raise_already_invoked_error_if_necessary(__method__)
-
self.inner_implementation_action = block
-
set_expected_received_count :at_most, n
-
self
-
end
-
-
# Syntactic sugar for `exactly`, `at_least` and `at_most`
-
#
-
# @return [MessageExpectation] self, to support further chaining.
-
# @example
-
# expect(dealer).to receive(:deal_card).exactly(10).times
-
# expect(dealer).to receive(:deal_card).at_least(10).times
-
# expect(dealer).to receive(:deal_card).at_most(10).times
-
1
def times(&block)
-
self.inner_implementation_action = block
-
self
-
end
-
-
# Expect a message not to be received at all.
-
#
-
# @return [MessageExpectation] self, to support further chaining.
-
# @example
-
# expect(car).to receive(:stop).never
-
1
def never
-
error_generator.raise_double_negation_error("expect(obj)") if negative?
-
@expected_received_count = 0
-
self
-
end
-
-
# Expect a message to be received exactly one time.
-
#
-
# @return [MessageExpectation] self, to support further chaining.
-
# @example
-
# expect(car).to receive(:go).once
-
1
def once(&block)
-
self.inner_implementation_action = block
-
set_expected_received_count :exactly, 1
-
self
-
end
-
-
# Expect a message to be received exactly two times.
-
#
-
# @return [MessageExpectation] self, to support further chaining.
-
# @example
-
# expect(car).to receive(:go).twice
-
1
def twice(&block)
-
self.inner_implementation_action = block
-
set_expected_received_count :exactly, 2
-
self
-
end
-
-
# Expect a message to be received exactly three times.
-
#
-
# @return [MessageExpectation] self, to support further chaining.
-
# @example
-
# expect(car).to receive(:go).thrice
-
1
def thrice(&block)
-
self.inner_implementation_action = block
-
set_expected_received_count :exactly, 3
-
self
-
end
-
# @!endgroup
-
-
# @!group Other Constraints
-
-
# Constrains a stub or message expectation to invocations with specific
-
# arguments.
-
#
-
# With a stub, if the message might be received with other args as well,
-
# you should stub a default value first, and then stub or mock the same
-
# message using `with` to constrain to specific arguments.
-
#
-
# A message expectation will fail if the message is received with different
-
# arguments.
-
#
-
# @return [MessageExpectation] self, to support further chaining.
-
# @example
-
# allow(cart).to receive(:add) { :failure }
-
# allow(cart).to receive(:add).with(Book.new(:isbn => 1934356379)) { :success }
-
# cart.add(Book.new(:isbn => 1234567890))
-
# # => :failure
-
# cart.add(Book.new(:isbn => 1934356379))
-
# # => :success
-
#
-
# expect(cart).to receive(:add).with(Book.new(:isbn => 1934356379)) { :success }
-
# cart.add(Book.new(:isbn => 1234567890))
-
# # => failed expectation
-
# cart.add(Book.new(:isbn => 1934356379))
-
# # => passes
-
1
def with(*args, &block)
-
raise_already_invoked_error_if_necessary(__method__)
-
if args.empty?
-
raise ArgumentError,
-
"`with` must have at least one argument. Use `no_args` matcher to set the expectation of receiving no arguments."
-
end
-
-
self.inner_implementation_action = block
-
@argument_list_matcher = ArgumentListMatcher.new(*args)
-
self
-
end
-
-
# Expect messages to be received in a specific order.
-
#
-
# @return [MessageExpectation] self, to support further chaining.
-
# @example
-
# expect(api).to receive(:prepare).ordered
-
# expect(api).to receive(:run).ordered
-
# expect(api).to receive(:finish).ordered
-
1
def ordered(&block)
-
self.inner_implementation_action = block
-
additional_expected_calls.times do
-
@order_group.register(self)
-
end
-
@ordered = true
-
self
-
end
-
-
# @private
-
# Contains the parts of `MessageExpectation` that aren't part of
-
# rspec-mocks' public API. The class is very big and could really use
-
# some collaborators it delegates to for this stuff but for now this was
-
# the simplest way to split the public from private stuff to make it
-
# easier to publish the docs for the APIs we want published.
-
1
module ImplementationDetails
-
1
attr_accessor :error_generator, :implementation
-
1
attr_reader :message
-
1
attr_reader :orig_object
-
1
attr_writer :expected_received_count, :expected_from, :argument_list_matcher
-
1
protected :expected_received_count=, :expected_from=, :error_generator, :error_generator=, :implementation=
-
-
# rubocop:disable Style/ParameterLists
-
1
def initialize(error_generator, expectation_ordering, expected_from, method_double,
-
type=:expectation, opts={}, &implementation_block)
-
@error_generator = error_generator
-
@error_generator.opts = opts
-
@expected_from = expected_from
-
@method_double = method_double
-
@orig_object = @method_double.object
-
@message = @method_double.method_name
-
@actual_received_count = 0
-
@expected_received_count = type == :expectation ? 1 : :any
-
@argument_list_matcher = ArgumentListMatcher::MATCH_ALL
-
@order_group = expectation_ordering
-
@order_group.register(self) unless type == :stub
-
@expectation_type = type
-
@ordered = false
-
@at_least = @at_most = @exactly = nil
-
-
# Initialized to nil so that we don't allocate an array for every
-
# mock or stub. See also comment in `and_yield`.
-
@args_to_yield = nil
-
@eval_context = nil
-
@yield_receiver_to_implementation_block = false
-
-
@implementation = Implementation.new
-
self.inner_implementation_action = implementation_block
-
end
-
# rubocop:enable Style/ParameterLists
-
-
1
def expected_args
-
@argument_list_matcher.expected_args
-
end
-
-
1
def and_yield_receiver_to_implementation
-
@yield_receiver_to_implementation_block = true
-
self
-
end
-
-
1
def yield_receiver_to_implementation_block?
-
@yield_receiver_to_implementation_block
-
end
-
-
1
def matches?(message, *args)
-
@message == message && @argument_list_matcher.args_match?(*args)
-
end
-
-
1
def safe_invoke(parent_stub, *args, &block)
-
invoke_incrementing_actual_calls_by(1, false, parent_stub, *args, &block)
-
end
-
-
1
def invoke(parent_stub, *args, &block)
-
invoke_incrementing_actual_calls_by(1, true, parent_stub, *args, &block)
-
end
-
-
1
def invoke_without_incrementing_received_count(parent_stub, *args, &block)
-
invoke_incrementing_actual_calls_by(0, true, parent_stub, *args, &block)
-
end
-
-
1
def negative?
-
@expected_received_count == 0 && !@at_least
-
end
-
-
1
def called_max_times?
-
@expected_received_count != :any &&
-
!@at_least &&
-
@expected_received_count > 0 &&
-
@actual_received_count >= @expected_received_count
-
end
-
-
1
def matches_name_but_not_args(message, *args)
-
@message == message && !@argument_list_matcher.args_match?(*args)
-
end
-
-
1
def verify_messages_received
-
return if expected_messages_received?
-
generate_error
-
end
-
-
1
def expected_messages_received?
-
ignoring_args? || matches_exact_count? || matches_at_least_count? || matches_at_most_count?
-
end
-
-
1
def ensure_expected_ordering_received!
-
@order_group.verify_invocation_order(self) if @ordered
-
true
-
end
-
-
1
def ignoring_args?
-
@expected_received_count == :any
-
end
-
-
1
def matches_at_least_count?
-
@at_least && @actual_received_count >= @expected_received_count
-
end
-
-
1
def matches_at_most_count?
-
@at_most && @actual_received_count <= @expected_received_count
-
end
-
-
1
def matches_exact_count?
-
@expected_received_count == @actual_received_count
-
end
-
-
1
def similar_messages
-
@similar_messages ||= []
-
end
-
-
1
def advise(*args)
-
similar_messages << args
-
end
-
-
1
def unadvise(args)
-
similar_messages.delete_if { |message| args.include?(message) }
-
end
-
-
1
def generate_error
-
if similar_messages.empty?
-
@error_generator.raise_expectation_error(
-
@message, @expected_received_count, @argument_list_matcher,
-
@actual_received_count, expectation_count_type, expected_args,
-
@expected_from, exception_source_id
-
)
-
else
-
@error_generator.raise_similar_message_args_error(
-
self, @similar_messages, @expected_from
-
)
-
end
-
end
-
-
1
def raise_unexpected_message_args_error(args_for_multiple_calls)
-
@error_generator.raise_unexpected_message_args_error(self, args_for_multiple_calls, exception_source_id)
-
end
-
-
1
def expectation_count_type
-
return :at_least if @at_least
-
return :at_most if @at_most
-
nil
-
end
-
-
1
def description_for(verb)
-
@error_generator.describe_expectation(
-
verb, @message, @expected_received_count,
-
@actual_received_count, expected_args
-
)
-
end
-
-
1
def raise_out_of_order_error
-
@error_generator.raise_out_of_order_error @message
-
end
-
-
1
def additional_expected_calls
-
return 0 if @expectation_type == :stub || !@exactly
-
@expected_received_count - 1
-
end
-
-
1
def ordered?
-
@ordered
-
end
-
-
1
def negative_expectation_for?(message)
-
@message == message && negative?
-
end
-
-
1
def actual_received_count_matters?
-
@at_least || @at_most || @exactly
-
end
-
-
1
def increase_actual_received_count!
-
@actual_received_count += 1
-
end
-
-
1
private
-
-
1
def exception_source_id
-
@exception_source_id ||= "#{self.class.name} #{__id__}"
-
end
-
-
1
def invoke_incrementing_actual_calls_by(increment, allowed_to_fail, parent_stub, *args, &block)
-
args.unshift(orig_object) if yield_receiver_to_implementation_block?
-
-
if negative? || (allowed_to_fail && (@exactly || @at_most) && (@actual_received_count == @expected_received_count))
-
# args are the args we actually received, @argument_list_matcher is the
-
# list of args we were expecting
-
@error_generator.raise_expectation_error(
-
@message, @expected_received_count,
-
@argument_list_matcher,
-
@actual_received_count + increment,
-
expectation_count_type, args, nil, exception_source_id
-
)
-
end
-
-
@order_group.handle_order_constraint self
-
-
if implementation.present?
-
implementation.call(*args, &block)
-
elsif parent_stub
-
parent_stub.invoke(nil, *args, &block)
-
end
-
ensure
-
@actual_received_count += increment
-
end
-
-
1
def has_been_invoked?
-
@actual_received_count > 0
-
end
-
-
1
def raise_already_invoked_error_if_necessary(calling_customization)
-
return unless has_been_invoked?
-
-
error_generator.raise_already_invoked_error(message, calling_customization)
-
end
-
-
1
def set_expected_received_count(relativity, n)
-
@at_least = (relativity == :at_least)
-
@at_most = (relativity == :at_most)
-
@exactly = (relativity == :exactly)
-
@expected_received_count = case n
-
when Numeric then n
-
when :once then 1
-
when :twice then 2
-
when :thrice then 3
-
end
-
end
-
-
1
def initial_implementation_action=(action)
-
implementation.initial_action = action
-
end
-
-
1
def inner_implementation_action=(action)
-
return unless action
-
warn_about_stub_override if implementation.inner_action
-
implementation.inner_action = action
-
end
-
-
1
def terminal_implementation_action=(action)
-
implementation.terminal_action = action
-
end
-
-
1
def warn_about_stub_override
-
RSpec.warning(
-
"You're overriding a previous stub implementation of `#{@message}`. " \
-
"Called from #{CallerFilter.first_non_rspec_line}."
-
)
-
end
-
end
-
-
1
include ImplementationDetails
-
end
-
-
# Handles the implementation of an `and_yield` declaration.
-
# @private
-
1
class AndYieldImplementation
-
1
def initialize(args_to_yield, eval_context, error_generator)
-
@args_to_yield = args_to_yield
-
@eval_context = eval_context
-
@error_generator = error_generator
-
end
-
-
1
def call(*_args_to_ignore, &block)
-
return if @args_to_yield.empty? && @eval_context.nil?
-
-
@error_generator.raise_missing_block_error @args_to_yield unless block
-
value = nil
-
block_signature = Support::BlockSignature.new(block)
-
-
@args_to_yield.each do |args|
-
unless Support::StrictSignatureVerifier.new(block_signature, args).valid?
-
@error_generator.raise_wrong_arity_error(args, block_signature)
-
end
-
-
value = @eval_context ? @eval_context.instance_exec(*args, &block) : block.call(*args)
-
end
-
value
-
end
-
end
-
-
# Handles the implementation of an `and_return` implementation.
-
# @private
-
1
class AndReturnImplementation
-
1
def initialize(values_to_return)
-
@values_to_return = values_to_return
-
end
-
-
1
def call(*_args_to_ignore, &_block)
-
if @values_to_return.size > 1
-
@values_to_return.shift
-
else
-
@values_to_return.first
-
end
-
end
-
end
-
-
# Represents a configured implementation. Takes into account
-
# any number of sub-implementations.
-
# @private
-
1
class Implementation
-
1
attr_accessor :initial_action, :inner_action, :terminal_action
-
-
1
def call(*args, &block)
-
actions.map do |action|
-
action.call(*args, &block)
-
end.last
-
end
-
-
1
def present?
-
actions.any?
-
end
-
-
1
private
-
-
1
def actions
-
[initial_action, inner_action, terminal_action].compact
-
end
-
end
-
-
# Represents an `and_call_original` implementation.
-
# @private
-
1
class AndWrapOriginalImplementation
-
1
def initialize(method, block)
-
@method = method
-
@block = block
-
end
-
-
1
CannotModifyFurtherError = Class.new(StandardError)
-
-
1
def initial_action=(_value)
-
raise cannot_modify_further_error
-
end
-
-
1
def inner_action=(_value)
-
raise cannot_modify_further_error
-
end
-
-
1
def terminal_action=(_value)
-
raise cannot_modify_further_error
-
end
-
-
1
def present?
-
true
-
end
-
-
1
def inner_action
-
true
-
end
-
-
1
def call(*args, &block)
-
@block.call(@method, *args, &block)
-
end
-
-
1
private
-
-
1
def cannot_modify_further_error
-
CannotModifyFurtherError.new "This method has already been configured " \
-
"to call the original implementation, and cannot be modified further."
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
class MethodDouble
-
# @private
-
1
attr_reader :method_name, :object, :expectations, :stubs, :method_stasher
-
-
# @private
-
1
def initialize(object, method_name, proxy)
-
@method_name = method_name
-
@object = object
-
@proxy = proxy
-
-
@original_visibility = nil
-
@method_stasher = InstanceMethodStasher.new(object, method_name)
-
@method_is_proxied = false
-
@expectations = []
-
@stubs = []
-
end
-
-
1
def original_implementation_callable
-
# If original method is not present, uses the `method_missing`
-
# handler of the object. This accounts for cases where the user has not
-
# correctly defined `respond_to?`, and also 1.8 which does not provide
-
# method handles for missing methods even if `respond_to?` is correct.
-
@original_implementation_callable ||= original_method ||
-
Proc.new do |*args, &block|
-
@object.__send__(:method_missing, @method_name, *args, &block)
-
end
-
end
-
-
1
alias_method :save_original_implementation_callable!, :original_implementation_callable
-
-
1
def original_method
-
@original_method ||=
-
@method_stasher.original_method ||
-
@proxy.original_method_handle_for(method_name)
-
end
-
-
# @private
-
1
def visibility
-
@proxy.visibility_for(@method_name)
-
end
-
-
# @private
-
1
def object_singleton_class
-
class << @object; self; end
-
end
-
-
# @private
-
1
def configure_method
-
@original_visibility = visibility
-
@method_stasher.stash unless @method_is_proxied
-
define_proxy_method
-
end
-
-
# @private
-
1
def define_proxy_method
-
return if @method_is_proxied
-
-
save_original_implementation_callable!
-
definition_target.class_exec(self, method_name, visibility) do |method_double, method_name, visibility|
-
define_method(method_name) do |*args, &block|
-
method_double.proxy_method_invoked(self, *args, &block)
-
end
-
__send__(visibility, method_name)
-
end
-
-
@method_is_proxied = true
-
end
-
-
# The implementation of the proxied method. Subclasses may override this
-
# method to perform additional operations.
-
#
-
# @private
-
1
def proxy_method_invoked(_obj, *args, &block)
-
@proxy.message_received method_name, *args, &block
-
end
-
-
# @private
-
1
def restore_original_method
-
return show_frozen_warning if object_singleton_class.frozen?
-
return unless @method_is_proxied
-
-
remove_method_from_definition_target
-
@method_stasher.restore if @method_stasher.method_is_stashed?
-
restore_original_visibility
-
-
@method_is_proxied = false
-
end
-
-
# @private
-
1
def show_frozen_warning
-
RSpec.warn_with(
-
"WARNING: rspec-mocks was unable to restore the original `#{@method_name}` " \
-
"method on #{@object.inspect} because it has been frozen. If you reuse this " \
-
"object, `#{@method_name}` will continue to respond with its stub implementation.",
-
:call_site => nil,
-
:use_spec_location_as_call_site => true
-
)
-
end
-
-
# @private
-
1
def restore_original_visibility
-
return unless @original_visibility &&
-
MethodReference.method_defined_at_any_visibility?(object_singleton_class, @method_name)
-
-
object_singleton_class.__send__(@original_visibility, method_name)
-
end
-
-
# @private
-
1
def verify
-
expectations.each { |e| e.verify_messages_received }
-
end
-
-
# @private
-
1
def reset
-
restore_original_method
-
clear
-
end
-
-
# @private
-
1
def clear
-
expectations.clear
-
stubs.clear
-
end
-
-
# The type of message expectation to create has been extracted to its own
-
# method so that subclasses can override it.
-
#
-
# @private
-
1
def message_expectation_class
-
MessageExpectation
-
end
-
-
# @private
-
1
def add_expectation(error_generator, expectation_ordering, expected_from, opts, &implementation)
-
configure_method
-
expectation = message_expectation_class.new(error_generator, expectation_ordering,
-
expected_from, self, :expectation, opts, &implementation)
-
expectations << expectation
-
expectation
-
end
-
-
# @private
-
1
def build_expectation(error_generator, expectation_ordering)
-
expected_from = IGNORED_BACKTRACE_LINE
-
message_expectation_class.new(error_generator, expectation_ordering, expected_from, self)
-
end
-
-
# @private
-
1
def add_stub(error_generator, expectation_ordering, expected_from, opts={}, &implementation)
-
configure_method
-
stub = message_expectation_class.new(error_generator, expectation_ordering, expected_from,
-
self, :stub, opts, &implementation)
-
stubs.unshift stub
-
stub
-
end
-
-
# A simple stub can only return a concrete value for a message, and
-
# cannot match on arguments. It is used as an optimization over
-
# `add_stub` / `add_expectation` where it is known in advance that this
-
# is all that will be required of a stub, such as when passing attributes
-
# to the `double` example method. They do not stash or restore existing method
-
# definitions.
-
#
-
# @private
-
1
def add_simple_stub(method_name, response)
-
setup_simple_method_double method_name, response, stubs
-
end
-
-
# @private
-
1
def add_simple_expectation(method_name, response, error_generator, backtrace_line)
-
setup_simple_method_double method_name, response, expectations, error_generator, backtrace_line
-
end
-
-
# @private
-
1
def setup_simple_method_double(method_name, response, collection, error_generator=nil, backtrace_line=nil)
-
define_proxy_method
-
-
me = SimpleMessageExpectation.new(method_name, response, error_generator, backtrace_line)
-
collection.unshift me
-
me
-
end
-
-
# @private
-
1
def add_default_stub(*args, &implementation)
-
return if stubs.any?
-
add_stub(*args, &implementation)
-
end
-
-
# @private
-
1
def remove_stub
-
raise_method_not_stubbed_error if stubs.empty?
-
remove_stub_if_present
-
end
-
-
# @private
-
1
def remove_stub_if_present
-
expectations.empty? ? reset : stubs.clear
-
end
-
-
# @private
-
1
def raise_method_not_stubbed_error
-
RSpec::Mocks.error_generator.raise_method_not_stubbed_error(method_name)
-
end
-
-
# In Ruby 2.0.0 and above prepend will alter the method lookup chain.
-
# We use an object's singleton class to define method doubles upon,
-
# however if the object has had it's singleton class (as opposed to
-
# it's actual class) prepended too then the the method lookup chain
-
# will look in the prepended module first, **before** the singleton
-
# class.
-
#
-
# This code works around that by providing a mock definition target
-
# that is either the singleton class, or if necessary, a prepended module
-
# of our own.
-
#
-
1
if Support::RubyFeatures.module_prepends_supported?
-
-
1
private
-
-
# We subclass `Module` in order to be able to easily detect our prepended module.
-
1
RSpecPrependedModule = Class.new(Module)
-
-
1
def definition_target
-
@definition_target ||= usable_rspec_prepended_module || object_singleton_class
-
end
-
-
1
def usable_rspec_prepended_module
-
@proxy.prepended_modules_of_singleton_class.each do |mod|
-
# If we have one of our modules prepended before one of the user's
-
# modules that defines the method, use that, since our module's
-
# definition will take precedence.
-
return mod if RSpecPrependedModule === mod
-
-
# If we hit a user module with the method defined first,
-
# we must create a new prepend module, even if one exists later,
-
# because ours will only take precedence if it comes first.
-
return new_rspec_prepended_module if mod.method_defined?(method_name)
-
end
-
-
nil
-
end
-
-
1
def new_rspec_prepended_module
-
RSpecPrependedModule.new.tap do |mod|
-
object_singleton_class.__send__ :prepend, mod
-
end
-
end
-
-
else
-
-
private
-
-
def definition_target
-
object_singleton_class
-
end
-
-
end
-
-
1
private
-
-
1
def remove_method_from_definition_target
-
definition_target.__send__(:remove_method, @method_name)
-
rescue NameError
-
# This can happen when the method has been monkeyed with by
-
# something outside RSpec. This happens, for example, when
-
# `file.write` has been stubbed, and then `file.reopen(other_io)`
-
# is later called, as `File#reopen` appears to redefine `write`.
-
#
-
# Note: we could avoid rescuing this by checking
-
# `definition_target.instance_method(@method_name).owner == definition_target`,
-
# saving us from the cost of the expensive exception, but this error is
-
# extremely rare (it was discovered on 2014-12-30, only happens on
-
# RUBY_VERSION < 2.0 and our spec suite only hits this condition once),
-
# so we'd rather avoid the cost of that check for every method double,
-
# and risk the rare situation where this exception will get raised.
-
RSpec.warn_with(
-
"WARNING: RSpec could not fully restore #{@object.inspect}." \
-
"#{@method_name}, possibly because the method has been redefined " \
-
"by something outside of RSpec."
-
)
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# Represents a method on an object that may or may not be defined.
-
# The method may be an instance method on a module or a method on
-
# any object.
-
#
-
# @private
-
1
class MethodReference
-
1
def self.for(object_reference, method_name)
-
new(object_reference, method_name)
-
end
-
-
1
def initialize(object_reference, method_name)
-
@object_reference = object_reference
-
@method_name = method_name
-
end
-
-
# A method is implemented if sending the message does not result in
-
# a `NoMethodError`. It might be dynamically implemented by
-
# `method_missing`.
-
1
def implemented?
-
@object_reference.when_loaded do |m|
-
method_implemented?(m)
-
end
-
end
-
-
# Returns true if we definitively know that sending the method
-
# will result in a `NoMethodError`.
-
#
-
# This is not simply the inverse of `implemented?`: there are
-
# cases when we don't know if a method is implemented and
-
# both `implemented?` and `unimplemented?` will return false.
-
1
def unimplemented?
-
@object_reference.when_loaded do |_m|
-
return !implemented?
-
end
-
-
# If it's not loaded, then it may be implemented but we can't check.
-
false
-
end
-
-
# A method is defined if we are able to get a `Method` object for it.
-
# In that case, we can assert against metadata like the arity.
-
1
def defined?
-
@object_reference.when_loaded do |m|
-
method_defined?(m)
-
end
-
end
-
-
1
def with_signature
-
return unless (original = original_method)
-
yield Support::MethodSignature.new(original)
-
end
-
-
1
def visibility
-
@object_reference.when_loaded do |m|
-
return visibility_from(m)
-
end
-
-
# When it's not loaded, assume it's public. We don't want to
-
# wrongly treat the method as private.
-
:public
-
end
-
-
1
private
-
-
1
def original_method
-
@object_reference.when_loaded do |m|
-
self.defined? && find_method(m)
-
end
-
end
-
-
1
def self.instance_method_visibility_for(klass, method_name)
-
if klass.public_method_defined?(method_name)
-
:public
-
elsif klass.private_method_defined?(method_name)
-
:private
-
elsif klass.protected_method_defined?(method_name)
-
:protected
-
end
-
end
-
-
1
class << self
-
1
alias method_defined_at_any_visibility? instance_method_visibility_for
-
end
-
-
1
def self.method_visibility_for(object, method_name)
-
instance_method_visibility_for(class << object; self; end, method_name).tap do |vis|
-
# If the method is not defined on the class, `instance_method_visibility_for`
-
# returns `nil`. However, it may be handled dynamically by `method_missing`,
-
# so here we check `respond_to` (passing false to not check private methods).
-
#
-
# This only considers the public case, but I don't think it's possible to
-
# write `method_missing` in such a way that it handles a dynamic message
-
# with private or protected visibility. Ruby doesn't provide you with
-
# the caller info.
-
return :public if vis.nil? && object.respond_to?(method_name, false)
-
end
-
end
-
end
-
-
# @private
-
1
class InstanceMethodReference < MethodReference
-
1
private
-
-
1
def method_implemented?(mod)
-
MethodReference.method_defined_at_any_visibility?(mod, @method_name)
-
end
-
-
# Ideally, we'd use `respond_to?` for `method_implemented?` but we need a
-
# reference to an instance to do that and we don't have one. Note that
-
# we may get false negatives: if the method is implemented via
-
# `method_missing`, we'll return `false` even though it meets our
-
# definition of "implemented". However, it's the best we can do.
-
1
alias method_defined? method_implemented?
-
-
# works around the fact that repeated calls for method parameters will
-
# falsely return empty arrays on JRuby in certain circumstances, this
-
# is necessary here because we can't dup/clone UnboundMethods.
-
#
-
# This is necessary due to a bug in JRuby prior to 1.7.5 fixed in:
-
# https://github.com/jruby/jruby/commit/99a0613fe29935150d76a9a1ee4cf2b4f63f4a27
-
1
if RUBY_PLATFORM == 'java' && JRUBY_VERSION.split('.')[-1].to_i < 5
-
def find_method(mod)
-
mod.dup.instance_method(@method_name)
-
end
-
else
-
1
def find_method(mod)
-
mod.instance_method(@method_name)
-
end
-
end
-
-
1
def visibility_from(mod)
-
MethodReference.instance_method_visibility_for(mod, @method_name)
-
end
-
end
-
-
# @private
-
1
class ObjectMethodReference < MethodReference
-
1
def self.for(object_reference, method_name)
-
if ClassNewMethodReference.applies_to?(method_name) { object_reference.when_loaded { |o| o } }
-
ClassNewMethodReference.new(object_reference, method_name)
-
else
-
super
-
end
-
end
-
-
1
private
-
-
1
def method_implemented?(object)
-
object.respond_to?(@method_name, true)
-
end
-
-
1
def method_defined?(object)
-
(class << object; self; end).method_defined?(@method_name)
-
end
-
-
1
def find_method(object)
-
object.method(@method_name)
-
end
-
-
1
def visibility_from(object)
-
MethodReference.method_visibility_for(object, @method_name)
-
end
-
end
-
-
# When a class's `.new` method is stubbed, we want to use the method
-
# signature from `#initialize` because `.new`'s signature is a generic
-
# `def new(*args)` and it simply delegates to `#initialize` and forwards
-
# all args...so the method with the actually used signature is `#initialize`.
-
#
-
# This method reference implementation handles that specific case.
-
# @private
-
1
class ClassNewMethodReference < ObjectMethodReference
-
1
def self.applies_to?(method_name)
-
return false unless method_name == :new
-
klass = yield
-
return false unless klass.respond_to?(:new, true)
-
-
# We only want to apply our special logic to normal `new` methods.
-
# Methods that the user has monkeyed with should be left as-is.
-
klass.method(:new).owner == ::Class
-
end
-
-
1
def with_signature
-
@object_reference.when_loaded do |klass|
-
yield Support::MethodSignature.new(klass.instance_method(:initialize))
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_support 'recursive_const_methods'
-
-
1
module RSpec
-
1
module Mocks
-
# Provides information about constants that may (or may not)
-
# have been mutated by rspec-mocks.
-
1
class Constant
-
1
extend Support::RecursiveConstMethods
-
-
# @api private
-
1
def initialize(name)
-
@name = name
-
@previously_defined = false
-
@stubbed = false
-
@hidden = false
-
@valid_name = true
-
yield self if block_given?
-
end
-
-
# @return [String] The fully qualified name of the constant.
-
1
attr_reader :name
-
-
# @return [Object, nil] The original value (e.g. before it
-
# was mutated by rspec-mocks) of the constant, or
-
# nil if the constant was not previously defined.
-
1
attr_accessor :original_value
-
-
# @private
-
1
attr_writer :previously_defined, :stubbed, :hidden, :valid_name
-
-
# @return [Boolean] Whether or not the constant was defined
-
# before the current example.
-
1
def previously_defined?
-
@previously_defined
-
end
-
-
# @return [Boolean] Whether or not rspec-mocks has mutated
-
# (stubbed or hidden) this constant.
-
1
def mutated?
-
@stubbed || @hidden
-
end
-
-
# @return [Boolean] Whether or not rspec-mocks has stubbed
-
# this constant.
-
1
def stubbed?
-
@stubbed
-
end
-
-
# @return [Boolean] Whether or not rspec-mocks has hidden
-
# this constant.
-
1
def hidden?
-
@hidden
-
end
-
-
# @return [Boolean] Whether or not the provided constant name
-
# is a valid Ruby constant name.
-
1
def valid_name?
-
@valid_name
-
end
-
-
# The default `to_s` isn't very useful, so a custom version is provided.
-
1
def to_s
-
"#<#{self.class.name} #{name}>"
-
end
-
1
alias inspect to_s
-
-
# @private
-
1
def self.unmutated(name)
-
previously_defined = recursive_const_defined?(name)
-
rescue NameError
-
new(name) do |c|
-
c.valid_name = false
-
end
-
else
-
new(name) do |const|
-
const.previously_defined = previously_defined
-
const.original_value = recursive_const_get(name) if previously_defined
-
end
-
end
-
-
# Queries rspec-mocks to find out information about the named constant.
-
#
-
# @param [String] name the name of the constant
-
# @return [Constant] an object contaning information about the named
-
# constant.
-
1
def self.original(name)
-
mutator = ::RSpec::Mocks.space.constant_mutator_for(name)
-
mutator ? mutator.to_constant : unmutated(name)
-
end
-
end
-
-
# Provides a means to stub constants.
-
1
class ConstantMutator
-
1
extend Support::RecursiveConstMethods
-
-
# Stubs a constant.
-
#
-
# @param (see ExampleMethods#stub_const)
-
# @option (see ExampleMethods#stub_const)
-
# @return (see ExampleMethods#stub_const)
-
#
-
# @see ExampleMethods#stub_const
-
# @note It's recommended that you use `stub_const` in your
-
# examples. This is an alternate public API that is provided
-
# so you can stub constants in other contexts (e.g. helper
-
# classes).
-
1
def self.stub(constant_name, value, options={})
-
mutator = if recursive_const_defined?(constant_name, &raise_on_invalid_const)
-
DefinedConstantReplacer
-
else
-
UndefinedConstantSetter
-
end
-
-
mutate(mutator.new(constant_name, value, options[:transfer_nested_constants]))
-
value
-
end
-
-
# Hides a constant.
-
#
-
# @param (see ExampleMethods#hide_const)
-
#
-
# @see ExampleMethods#hide_const
-
# @note It's recommended that you use `hide_const` in your
-
# examples. This is an alternate public API that is provided
-
# so you can hide constants in other contexts (e.g. helper
-
# classes).
-
1
def self.hide(constant_name)
-
mutate(ConstantHider.new(constant_name, nil, {}))
-
nil
-
end
-
-
# Contains common functionality used by all of the constant mutators.
-
#
-
# @private
-
1
class BaseMutator
-
1
include Support::RecursiveConstMethods
-
-
1
attr_reader :original_value, :full_constant_name
-
-
1
def initialize(full_constant_name, mutated_value, transfer_nested_constants)
-
@full_constant_name = normalize_const_name(full_constant_name)
-
@mutated_value = mutated_value
-
@transfer_nested_constants = transfer_nested_constants
-
@context_parts = @full_constant_name.split('::')
-
@const_name = @context_parts.pop
-
@reset_performed = false
-
end
-
-
1
def to_constant
-
const = Constant.new(full_constant_name)
-
const.original_value = original_value
-
-
const
-
end
-
-
1
def idempotently_reset
-
reset unless @reset_performed
-
@reset_performed = true
-
end
-
end
-
-
# Hides a defined constant for the duration of an example.
-
#
-
# @private
-
1
class ConstantHider < BaseMutator
-
1
def mutate
-
return unless (@defined = recursive_const_defined?(full_constant_name))
-
@context = recursive_const_get(@context_parts.join('::'))
-
@original_value = get_const_defined_on(@context, @const_name)
-
-
@context.__send__(:remove_const, @const_name)
-
end
-
-
1
def to_constant
-
return Constant.unmutated(full_constant_name) unless @defined
-
-
const = super
-
const.hidden = true
-
const.previously_defined = true
-
-
const
-
end
-
-
1
def reset
-
return unless @defined
-
@context.const_set(@const_name, @original_value)
-
end
-
end
-
-
# Replaces a defined constant for the duration of an example.
-
#
-
# @private
-
1
class DefinedConstantReplacer < BaseMutator
-
1
def initialize(*args)
-
super
-
@constants_to_transfer = []
-
end
-
-
1
def mutate
-
@context = recursive_const_get(@context_parts.join('::'))
-
@original_value = get_const_defined_on(@context, @const_name)
-
-
@constants_to_transfer = verify_constants_to_transfer!
-
-
@context.__send__(:remove_const, @const_name)
-
@context.const_set(@const_name, @mutated_value)
-
-
transfer_nested_constants
-
end
-
-
1
def to_constant
-
const = super
-
const.stubbed = true
-
const.previously_defined = true
-
-
const
-
end
-
-
1
def reset
-
@constants_to_transfer.each do |const|
-
@mutated_value.__send__(:remove_const, const)
-
end
-
-
@context.__send__(:remove_const, @const_name)
-
@context.const_set(@const_name, @original_value)
-
end
-
-
1
def transfer_nested_constants
-
@constants_to_transfer.each do |const|
-
@mutated_value.const_set(const, get_const_defined_on(original_value, const))
-
end
-
end
-
-
1
def verify_constants_to_transfer!
-
return [] unless should_transfer_nested_constants?
-
-
{ @original_value => "the original value", @mutated_value => "the stubbed value" }.each do |value, description|
-
next if value.respond_to?(:constants)
-
-
raise ArgumentError,
-
"Cannot transfer nested constants for #{@full_constant_name} " \
-
"since #{description} is not a class or module and only classes " \
-
"and modules support nested constants."
-
end
-
-
if Array === @transfer_nested_constants
-
@transfer_nested_constants = @transfer_nested_constants.map(&:to_s) if RUBY_VERSION == '1.8.7'
-
undefined_constants = @transfer_nested_constants - constants_defined_on(@original_value)
-
-
if undefined_constants.any?
-
available_constants = constants_defined_on(@original_value) - @transfer_nested_constants
-
raise ArgumentError,
-
"Cannot transfer nested constant(s) #{undefined_constants.join(' and ')} " \
-
"for #{@full_constant_name} since they are not defined. Did you mean " \
-
"#{available_constants.join(' or ')}?"
-
end
-
-
@transfer_nested_constants
-
else
-
constants_defined_on(@original_value)
-
end
-
end
-
-
1
def should_transfer_nested_constants?
-
return true if @transfer_nested_constants
-
return false unless RSpec::Mocks.configuration.transfer_nested_constants?
-
@original_value.respond_to?(:constants) && @mutated_value.respond_to?(:constants)
-
end
-
end
-
-
# Sets an undefined constant for the duration of an example.
-
#
-
# @private
-
1
class UndefinedConstantSetter < BaseMutator
-
1
def mutate
-
@parent = @context_parts.inject(Object) do |klass, name|
-
if const_defined_on?(klass, name)
-
get_const_defined_on(klass, name)
-
else
-
ConstantMutator.stub(name_for(klass, name), Module.new)
-
end
-
end
-
-
@parent.const_set(@const_name, @mutated_value)
-
end
-
-
1
def to_constant
-
const = super
-
const.stubbed = true
-
const.previously_defined = false
-
-
const
-
end
-
-
1
def reset
-
@parent.__send__(:remove_const, @const_name)
-
end
-
-
1
private
-
-
1
def name_for(parent, name)
-
root = if parent == Object
-
''
-
else
-
parent.name
-
end
-
root + '::' + name
-
end
-
end
-
-
# Uses the mutator to mutate (stub or hide) a constant. Ensures that
-
# the mutator is correctly registered so it can be backed out at the end
-
# of the test.
-
#
-
# @private
-
1
def self.mutate(mutator)
-
::RSpec::Mocks.space.register_constant_mutator(mutator)
-
mutator.mutate
-
end
-
-
# Used internally by the constant stubbing to raise a helpful
-
# error when a constant like "A::B::C" is stubbed and A::B is
-
# not a module (and thus, it's impossible to define "A::B::C"
-
# since only modules can have nested constants).
-
#
-
# @api private
-
1
def self.raise_on_invalid_const
-
lambda do |const_name, failed_name|
-
raise "Cannot stub constant #{failed_name} on #{const_name} " \
-
"since #{const_name} is not a module."
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
class ObjectReference
-
# Returns an appropriate Object or Module reference based
-
# on the given argument.
-
1
def self.for(object_module_or_name, allow_direct_object_refs=false)
-
case object_module_or_name
-
when Module
-
if anonymous_module?(object_module_or_name)
-
DirectObjectReference.new(object_module_or_name)
-
else
-
# Use a `NamedObjectReference` if it has a name because this
-
# will use the original value of the constant in case it has
-
# been stubbed.
-
NamedObjectReference.new(name_of(object_module_or_name))
-
end
-
when String
-
NamedObjectReference.new(object_module_or_name)
-
else
-
if allow_direct_object_refs
-
DirectObjectReference.new(object_module_or_name)
-
else
-
raise ArgumentError,
-
"Module or String expected, got #{object_module_or_name.inspect}"
-
end
-
end
-
end
-
-
1
if Module.new.name.nil?
-
1
def self.anonymous_module?(mod)
-
!name_of(mod)
-
end
-
else # 1.8.7
-
def self.anonymous_module?(mod)
-
name_of(mod) == ""
-
end
-
end
-
1
private_class_method :anonymous_module?
-
-
1
def self.name_of(mod)
-
MODULE_NAME_METHOD.bind(mod).call
-
end
-
1
private_class_method :name_of
-
-
# @private
-
1
MODULE_NAME_METHOD = Module.instance_method(:name)
-
end
-
-
# An implementation of rspec-mocks' reference interface.
-
# Used when an object is passed to {ExampleMethods#object_double}, or
-
# an anonymous class or module is passed to {ExampleMethods#instance_double}
-
# or {ExampleMethods#class_double}.
-
# Represents a reference to that object.
-
# @see NamedObjectReference
-
1
class DirectObjectReference
-
# @param object [Object] the object to which this refers
-
1
def initialize(object)
-
@object = object
-
end
-
-
# @return [String] the object's description (via `#inspect`).
-
1
def description
-
@object.inspect
-
end
-
-
# Defined for interface parity with the other object reference
-
# implementations. Raises an `ArgumentError` to indicate that `as_stubbed_const`
-
# is invalid when passing an object argument to `object_double`.
-
1
def const_to_replace
-
raise ArgumentError,
-
"Can not perform constant replacement with an anonymous object."
-
end
-
-
# The target of the verifying double (the object itself).
-
#
-
# @return [Object]
-
1
def target
-
@object
-
end
-
-
# Always returns true for an object as the class is defined.
-
#
-
# @return [true]
-
1
def defined?
-
true
-
end
-
-
# Yields if the reference target is loaded, providing a generic mechanism
-
# to optionally run a bit of code only when a reference's target is
-
# loaded.
-
#
-
# This specific implementation always yields because direct references
-
# are always loaded.
-
#
-
# @yield [Object] the target of this reference.
-
1
def when_loaded
-
yield @object
-
end
-
end
-
-
# An implementation of rspec-mocks' reference interface.
-
# Used when a string is passed to {ExampleMethods#object_double},
-
# and when a string, named class or named module is passed to
-
# {ExampleMethods#instance_double}, or {ExampleMethods#class_double}.
-
# Represents a reference to the object named (via a constant lookup)
-
# by the string.
-
# @see DirectObjectReference
-
1
class NamedObjectReference
-
# @param const_name [String] constant name
-
1
def initialize(const_name)
-
@const_name = const_name
-
end
-
-
# @return [Boolean] true if the named constant is defined, false otherwise.
-
1
def defined?
-
!!object
-
end
-
-
# @return [String] the constant name to replace with a double.
-
1
def const_to_replace
-
@const_name
-
end
-
1
alias description const_to_replace
-
-
# @return [Object, nil] the target of the verifying double (the named object), or
-
# nil if it is not defined.
-
1
def target
-
object
-
end
-
-
# Yields if the reference target is loaded, providing a generic mechanism
-
# to optionally run a bit of code only when a reference's target is
-
# loaded.
-
#
-
# @yield [Object] the target object
-
1
def when_loaded
-
yield object if object
-
end
-
-
1
private
-
-
1
def object
-
return @object if defined?(@object)
-
@object = Constant.original(@const_name).original_value
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
class OrderGroup
-
1
def initialize
-
28
@expectations = []
-
28
@invocation_order = []
-
28
@index = 0
-
end
-
-
# @private
-
1
def register(expectation)
-
@expectations << expectation
-
end
-
-
1
def invoked(message)
-
@invocation_order << message
-
end
-
-
# @private
-
1
def ready_for?(expectation)
-
remaining_expectations.find(&:ordered?) == expectation
-
end
-
-
# @private
-
1
def consume
-
remaining_expectations.each_with_index do |expectation, index|
-
next unless expectation.ordered?
-
-
@index += index + 1
-
return expectation
-
end
-
nil
-
end
-
-
# @private
-
1
def handle_order_constraint(expectation)
-
return unless expectation.ordered? && remaining_expectations.include?(expectation)
-
return consume if ready_for?(expectation)
-
expectation.raise_out_of_order_error
-
end
-
-
1
def verify_invocation_order(expectation)
-
expectation.raise_out_of_order_error unless expectations_invoked_in_order?
-
true
-
end
-
-
1
def clear
-
@index = 0
-
@invocation_order.clear
-
@expectations.clear
-
end
-
-
1
def empty?
-
@expectations.empty?
-
end
-
-
1
private
-
-
1
def remaining_expectations
-
@expectations[@index..-1] || []
-
end
-
-
1
def expectations_invoked_in_order?
-
invoked_expectations == expected_invocations
-
end
-
-
1
def invoked_expectations
-
@expectations.select { |e| e.ordered? && @invocation_order.include?(e) }
-
end
-
-
1
def expected_invocations
-
@invocation_order.map { |invocation| expectation_for(invocation) }.compact
-
end
-
-
1
def expectation_for(message)
-
@expectations.find { |e| message == e }
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
class Proxy
-
1
SpecificMessage = Struct.new(:object, :message, :args) do
-
1
def ==(expectation)
-
expectation.orig_object == object && expectation.matches?(message, *args)
-
end
-
end
-
-
# @private
-
1
def ensure_implemented(*_args)
-
# noop for basic proxies, see VerifyingProxy for behaviour.
-
end
-
-
# @private
-
1
def initialize(object, order_group, options={})
-
@object = object
-
@order_group = order_group
-
@error_generator = ErrorGenerator.new(object)
-
@messages_received = []
-
@options = options
-
@null_object = false
-
@method_doubles = Hash.new { |h, k| h[k] = MethodDouble.new(@object, k, self) }
-
end
-
-
# @private
-
1
attr_reader :object
-
-
# @private
-
1
def null_object?
-
@null_object
-
end
-
-
# @private
-
# Tells the object to ignore any messages that aren't explicitly set as
-
# stubs or message expectations.
-
1
def as_null_object
-
@null_object = true
-
@object
-
end
-
-
# @private
-
1
def original_method_handle_for(_message)
-
nil
-
end
-
-
1
DEFAULT_MESSAGE_EXPECTATION_OPTS = {}.freeze
-
-
# @private
-
1
def add_message_expectation(method_name, opts=DEFAULT_MESSAGE_EXPECTATION_OPTS, &block)
-
location = opts.fetch(:expected_from) { CallerFilter.first_non_rspec_line }
-
meth_double = method_double_for(method_name)
-
-
if null_object? && !block
-
meth_double.add_default_stub(@error_generator, @order_group, location, opts) do
-
@object
-
end
-
end
-
-
meth_double.add_expectation @error_generator, @order_group, location, opts, &block
-
end
-
-
# @private
-
1
def add_simple_expectation(method_name, response, location)
-
method_double_for(method_name).add_simple_expectation method_name, response, @error_generator, location
-
end
-
-
# @private
-
1
def build_expectation(method_name)
-
meth_double = method_double_for(method_name)
-
-
meth_double.build_expectation(
-
@error_generator,
-
@order_group
-
)
-
end
-
-
# @private
-
1
def replay_received_message_on(expectation, &block)
-
expected_method_name = expectation.message
-
meth_double = method_double_for(expected_method_name)
-
-
if meth_double.expectations.any?
-
@error_generator.raise_expectation_on_mocked_method(expected_method_name)
-
end
-
-
unless null_object? || meth_double.stubs.any?
-
@error_generator.raise_expectation_on_unstubbed_method(expected_method_name)
-
end
-
-
@messages_received.each do |(actual_method_name, args, received_block)|
-
next unless expectation.matches?(actual_method_name, *args)
-
-
expectation.safe_invoke(nil)
-
block.call(*args, &received_block) if block
-
end
-
end
-
-
# @private
-
1
def check_for_unexpected_arguments(expectation)
-
return if @messages_received.empty?
-
-
return if @messages_received.any? { |method_name, args, _| expectation.matches?(method_name, *args) }
-
-
name_but_not_args, others = @messages_received.partition do |(method_name, args, _)|
-
expectation.matches_name_but_not_args(method_name, *args)
-
end
-
-
return if name_but_not_args.empty? && !others.empty?
-
-
expectation.raise_unexpected_message_args_error(name_but_not_args.map { |args| args[1] })
-
end
-
-
# @private
-
1
def add_stub(method_name, opts={}, &implementation)
-
location = opts.fetch(:expected_from) { CallerFilter.first_non_rspec_line }
-
method_double_for(method_name).add_stub @error_generator, @order_group, location, opts, &implementation
-
end
-
-
# @private
-
1
def add_simple_stub(method_name, response)
-
method_double_for(method_name).add_simple_stub method_name, response
-
end
-
-
# @private
-
1
def remove_stub(method_name)
-
method_double_for(method_name).remove_stub
-
end
-
-
# @private
-
1
def remove_stub_if_present(method_name)
-
method_double_for(method_name).remove_stub_if_present
-
end
-
-
# @private
-
1
def verify
-
@method_doubles.each_value { |d| d.verify }
-
end
-
-
# @private
-
1
def reset
-
@messages_received.clear
-
end
-
-
# @private
-
1
def received_message?(method_name, *args, &block)
-
@messages_received.any? { |array| array == [method_name, args, block] }
-
end
-
-
# @private
-
1
def messages_arg_list
-
@messages_received.map { |_, args, _| args }
-
end
-
-
# @private
-
1
def has_negative_expectation?(message)
-
method_double_for(message).expectations.find { |expectation| expectation.negative_expectation_for?(message) }
-
end
-
-
# @private
-
1
def record_message_received(message, *args, &block)
-
@order_group.invoked SpecificMessage.new(object, message, args)
-
@messages_received << [message, args, block]
-
end
-
-
# @private
-
1
def message_received(message, *args, &block)
-
record_message_received message, *args, &block
-
-
expectation = find_matching_expectation(message, *args)
-
stub = find_matching_method_stub(message, *args)
-
-
if (stub && expectation && expectation.called_max_times?) || (stub && !expectation)
-
expectation.increase_actual_received_count! if expectation && expectation.actual_received_count_matters?
-
if (expectation = find_almost_matching_expectation(message, *args))
-
expectation.advise(*args) unless expectation.expected_messages_received?
-
end
-
stub.invoke(nil, *args, &block)
-
elsif expectation
-
expectation.unadvise(messages_arg_list)
-
expectation.invoke(stub, *args, &block)
-
elsif (expectation = find_almost_matching_expectation(message, *args))
-
expectation.advise(*args) if null_object? unless expectation.expected_messages_received?
-
-
if null_object? || !has_negative_expectation?(message)
-
expectation.raise_unexpected_message_args_error([args])
-
end
-
elsif (stub = find_almost_matching_stub(message, *args))
-
stub.advise(*args)
-
raise_missing_default_stub_error(stub, [args])
-
elsif Class === @object
-
@object.superclass.__send__(message, *args, &block)
-
else
-
@object.__send__(:method_missing, message, *args, &block)
-
end
-
end
-
-
# @private
-
1
def raise_unexpected_message_error(method_name, args)
-
@error_generator.raise_unexpected_message_error method_name, args
-
end
-
-
# @private
-
1
def raise_missing_default_stub_error(expectation, args_for_multiple_calls)
-
@error_generator.raise_missing_default_stub_error(expectation, args_for_multiple_calls)
-
end
-
-
# @private
-
1
def visibility_for(_method_name)
-
# This is the default (for test doubles). Subclasses override this.
-
:public
-
end
-
-
1
if Support::RubyFeatures.module_prepends_supported?
-
1
def self.prepended_modules_of(klass)
-
ancestors = klass.ancestors
-
-
# `|| 0` is necessary for Ruby 2.0, where the singleton class
-
# is only in the ancestor list when there are prepended modules.
-
singleton_index = ancestors.index(klass) || 0
-
-
ancestors[0, singleton_index]
-
end
-
-
1
def prepended_modules_of_singleton_class
-
@prepended_modules_of_singleton_class ||= RSpec::Mocks::Proxy.prepended_modules_of(@object.singleton_class)
-
end
-
end
-
-
1
private
-
-
1
def method_double_for(message)
-
@method_doubles[message.to_sym]
-
end
-
-
1
def find_matching_expectation(method_name, *args)
-
find_best_matching_expectation_for(method_name) do |expectation|
-
expectation.matches?(method_name, *args)
-
end
-
end
-
-
1
def find_almost_matching_expectation(method_name, *args)
-
find_best_matching_expectation_for(method_name) do |expectation|
-
expectation.matches_name_but_not_args(method_name, *args)
-
end
-
end
-
-
1
def find_best_matching_expectation_for(method_name)
-
first_match = nil
-
-
method_double_for(method_name).expectations.each do |expectation|
-
next unless yield expectation
-
return expectation unless expectation.called_max_times?
-
first_match ||= expectation
-
end
-
-
first_match
-
end
-
-
1
def find_matching_method_stub(method_name, *args)
-
method_double_for(method_name).stubs.find { |stub| stub.matches?(method_name, *args) }
-
end
-
-
1
def find_almost_matching_stub(method_name, *args)
-
method_double_for(method_name).stubs.find { |stub| stub.matches_name_but_not_args(method_name, *args) }
-
end
-
end
-
-
# @private
-
1
class TestDoubleProxy < Proxy
-
1
def reset
-
@method_doubles.clear
-
object.__disallow_further_usage!
-
super
-
end
-
end
-
-
# @private
-
1
class PartialDoubleProxy < Proxy
-
1
def original_method_handle_for(message)
-
if any_instance_class_recorder_observing_method?(@object.class, message)
-
message = ::RSpec::Mocks.space.
-
any_instance_recorder_for(@object.class).
-
build_alias_method_name(message)
-
end
-
-
::RSpec::Support.method_handle_for(@object, message)
-
rescue NameError
-
nil
-
end
-
-
# @private
-
1
def add_simple_expectation(method_name, response, location)
-
method_double_for(method_name).configure_method
-
super
-
end
-
-
# @private
-
1
def add_simple_stub(method_name, response)
-
method_double_for(method_name).configure_method
-
super
-
end
-
-
# @private
-
1
def visibility_for(method_name)
-
# We fall back to :public because by default we allow undefined methods
-
# to be stubbed, and when we do so, we make them public.
-
MethodReference.method_visibility_for(@object, method_name) || :public
-
end
-
-
1
def reset
-
@method_doubles.each_value { |d| d.reset }
-
super
-
end
-
-
1
def message_received(message, *args, &block)
-
RSpec::Mocks.space.any_instance_recorders_from_ancestry_of(object).each do |subscriber|
-
subscriber.notify_received_message(object, message, args, block)
-
end
-
super
-
end
-
-
1
private
-
-
1
def any_instance_class_recorder_observing_method?(klass, method_name)
-
only_return_existing = true
-
recorder = ::RSpec::Mocks.space.any_instance_recorder_for(klass, only_return_existing)
-
return true if recorder && recorder.already_observing?(method_name)
-
-
superklass = klass.superclass
-
return false if superklass.nil?
-
any_instance_class_recorder_observing_method?(superklass, method_name)
-
end
-
end
-
-
# @private
-
# When we mock or stub a method on a class, we have to treat it a bit different,
-
# because normally singleton method definitions only affect the object on which
-
# they are defined, but on classes they affect subclasses, too. As a result,
-
# we need some special handling to get the original method.
-
1
module PartialClassDoubleProxyMethods
-
1
def initialize(source_space, *args)
-
@source_space = source_space
-
super(*args)
-
end
-
-
# Consider this situation:
-
#
-
# class A; end
-
# class B < A; end
-
#
-
# allow(A).to receive(:new)
-
# expect(B).to receive(:new).and_call_original
-
#
-
# When getting the original definition for `B.new`, we cannot rely purely on
-
# using `B.method(:new)` before our redefinition is defined on `B`, because
-
# `B.method(:new)` will return a method that will execute the stubbed version
-
# of the method on `A` since singleton methods on classes are in the lookup
-
# hierarchy.
-
#
-
# To do it properly, we need to find the original definition of `new` from `A`
-
# from _before_ `A` was stubbed, and we need to rebind it to `B` so that it will
-
# run with the proper `self`.
-
#
-
# That's what this method (together with `original_unbound_method_handle_from_ancestor_for`)
-
# does.
-
1
def original_method_handle_for(message)
-
unbound_method = superclass_proxy &&
-
superclass_proxy.original_unbound_method_handle_from_ancestor_for(message.to_sym)
-
-
return super unless unbound_method
-
unbound_method.bind(object)
-
# :nocov:
-
skipped
rescue TypeError
-
skipped
if RUBY_VERSION == '1.8.7'
-
skipped
# In MRI 1.8.7, a singleton method on a class cannot be rebound to its subclass
-
skipped
if unbound_method && unbound_method.owner.ancestors.first != unbound_method.owner
-
skipped
# This is a singleton method; we can't do anything with it
-
skipped
# But we can work around this using a different implementation
-
skipped
double = method_double_from_ancestor_for(message)
-
skipped
return object.method(double.method_stasher.stashed_method_name)
-
skipped
end
-
skipped
end
-
skipped
raise
-
# :nocov:
-
end
-
-
1
protected
-
-
1
def original_unbound_method_handle_from_ancestor_for(message)
-
double = method_double_from_ancestor_for(message)
-
double && double.original_method.unbind
-
end
-
-
1
def method_double_from_ancestor_for(message)
-
@method_doubles.fetch(message) do
-
# The fact that there is no method double for this message indicates
-
# that it has not been redefined by rspec-mocks. We need to continue
-
# looking up the ancestor chain.
-
return superclass_proxy &&
-
superclass_proxy.method_double_from_ancestor_for(message)
-
end
-
end
-
-
1
def superclass_proxy
-
return @superclass_proxy if defined?(@superclass_proxy)
-
-
if (superclass = object.superclass)
-
@superclass_proxy = @source_space.superclass_proxy_for(superclass)
-
else
-
@superclass_proxy = nil
-
end
-
end
-
end
-
-
# @private
-
1
class PartialClassDoubleProxy < PartialDoubleProxy
-
1
include PartialClassDoubleProxyMethods
-
end
-
-
# @private
-
1
class ProxyForNil < PartialDoubleProxy
-
1
def initialize(order_group)
-
set_expectation_behavior
-
super(nil, order_group)
-
end
-
-
1
attr_accessor :disallow_expectations
-
1
attr_accessor :warn_about_expectations
-
-
1
def add_message_expectation(method_name, opts={}, &block)
-
warn_or_raise!(method_name)
-
super
-
end
-
-
1
def add_negative_message_expectation(location, method_name, &implementation)
-
warn_or_raise!(method_name)
-
super
-
end
-
-
1
def add_stub(method_name, opts={}, &implementation)
-
warn_or_raise!(method_name)
-
super
-
end
-
-
1
private
-
-
1
def set_expectation_behavior
-
case RSpec::Mocks.configuration.allow_message_expectations_on_nil
-
when false
-
@warn_about_expectations = false
-
@disallow_expectations = true
-
when true
-
@warn_about_expectations = false
-
@disallow_expectations = false
-
else
-
@warn_about_expectations = true
-
@disallow_expectations = false
-
end
-
end
-
-
1
def warn_or_raise!(method_name)
-
# This method intentionally swallows the message when
-
# neither disallow_expectations nor warn_about_expectations
-
# are set to true.
-
if disallow_expectations
-
raise_error(method_name)
-
elsif warn_about_expectations
-
warn(method_name)
-
end
-
end
-
-
1
def warn(method_name)
-
warning_msg = @error_generator.expectation_on_nil_message(method_name)
-
RSpec.warning(warning_msg)
-
end
-
-
1
def raise_error(method_name)
-
@error_generator.raise_expectation_on_nil_error(method_name)
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_support 'reentrant_mutex'
-
-
1
module RSpec
-
1
module Mocks
-
# @private
-
# Provides a default space implementation for outside
-
# the scope of an example. Called "root" because it serves
-
# as the root of the space stack.
-
1
class RootSpace
-
1
def proxy_for(*_args)
-
raise_lifecycle_message
-
end
-
-
1
def any_instance_recorder_for(*_args)
-
raise_lifecycle_message
-
end
-
-
1
def any_instance_proxy_for(*_args)
-
raise_lifecycle_message
-
end
-
-
1
def register_constant_mutator(_mutator)
-
raise_lifecycle_message
-
end
-
-
1
def any_instance_recorders_from_ancestry_of(_object)
-
raise_lifecycle_message
-
end
-
-
1
def reset_all
-
end
-
-
1
def verify_all
-
end
-
-
1
def registered?(_object)
-
false
-
end
-
-
1
def superclass_proxy_for(*_args)
-
raise_lifecycle_message
-
end
-
-
1
def new_scope
-
28
Space.new
-
end
-
-
1
private
-
-
1
def raise_lifecycle_message
-
raise OutsideOfExampleError,
-
"The use of doubles or partial doubles from rspec-mocks outside of the per-test lifecycle is not supported."
-
end
-
end
-
-
# @private
-
1
class Space
-
1
attr_reader :proxies, :any_instance_recorders, :proxy_mutex, :any_instance_mutex
-
-
1
def initialize
-
28
@proxies = {}
-
28
@any_instance_recorders = {}
-
28
@constant_mutators = []
-
28
@expectation_ordering = OrderGroup.new
-
28
@proxy_mutex = new_mutex
-
28
@any_instance_mutex = new_mutex
-
end
-
-
1
def new_scope
-
NestedSpace.new(self)
-
end
-
-
1
def verify_all
-
28
proxies.values.each { |proxy| proxy.verify }
-
28
any_instance_recorders.each_value { |recorder| recorder.verify }
-
end
-
-
1
def reset_all
-
28
proxies.each_value { |proxy| proxy.reset }
-
28
@constant_mutators.reverse.each { |mut| mut.idempotently_reset }
-
28
any_instance_recorders.each_value { |recorder| recorder.stop_all_observation! }
-
28
any_instance_recorders.clear
-
end
-
-
1
def register_constant_mutator(mutator)
-
@constant_mutators << mutator
-
end
-
-
1
def constant_mutator_for(name)
-
@constant_mutators.find { |m| m.full_constant_name == name }
-
end
-
-
1
def any_instance_recorder_for(klass, only_return_existing=false)
-
any_instance_mutex.synchronize do
-
id = klass.__id__
-
any_instance_recorders.fetch(id) do
-
return nil if only_return_existing
-
any_instance_recorder_not_found_for(id, klass)
-
end
-
end
-
end
-
-
1
def any_instance_proxy_for(klass)
-
AnyInstance::Proxy.new(any_instance_recorder_for(klass), proxies_of(klass))
-
end
-
-
1
def proxies_of(klass)
-
proxies.values.select { |proxy| klass === proxy.object }
-
end
-
-
1
def proxy_for(object)
-
proxy_mutex.synchronize do
-
id = id_for(object)
-
proxies.fetch(id) { proxy_not_found_for(id, object) }
-
end
-
end
-
-
1
def superclass_proxy_for(klass)
-
proxy_mutex.synchronize do
-
id = id_for(klass)
-
proxies.fetch(id) { superclass_proxy_not_found_for(id, klass) }
-
end
-
end
-
-
1
alias ensure_registered proxy_for
-
-
1
def registered?(object)
-
proxies.key?(id_for object)
-
end
-
-
1
def any_instance_recorders_from_ancestry_of(object)
-
# Optimization: `any_instance` is a feature we generally
-
# recommend not using, so we can often early exit here
-
# without doing an O(N) linear search over the number of
-
# ancestors in the object's class hierarchy.
-
return [] if any_instance_recorders.empty?
-
-
# We access the ancestors through the singleton class, to avoid calling
-
# `class` in case `class` has been stubbed.
-
(class << object; ancestors; end).map do |klass|
-
any_instance_recorders[klass.__id__]
-
end.compact
-
end
-
-
1
private
-
-
1
def new_mutex
-
56
Support::ReentrantMutex.new
-
end
-
-
1
def proxy_not_found_for(id, object)
-
proxies[id] = case object
-
when NilClass then ProxyForNil.new(@expectation_ordering)
-
when TestDouble then object.__build_mock_proxy_unless_expired(@expectation_ordering)
-
when Class
-
class_proxy_with_callback_verification_strategy(object, CallbackInvocationStrategy.new)
-
else
-
if RSpec::Mocks.configuration.verify_partial_doubles?
-
VerifyingPartialDoubleProxy.new(object, @expectation_ordering)
-
else
-
PartialDoubleProxy.new(object, @expectation_ordering)
-
end
-
end
-
end
-
-
1
def superclass_proxy_not_found_for(id, object)
-
raise "superclass_proxy_not_found_for called with something that is not a class" unless Class === object
-
proxies[id] = class_proxy_with_callback_verification_strategy(object, NoCallbackInvocationStrategy.new)
-
end
-
-
1
def class_proxy_with_callback_verification_strategy(object, strategy)
-
if RSpec::Mocks.configuration.verify_partial_doubles?
-
VerifyingPartialClassDoubleProxy.new(
-
self,
-
object,
-
@expectation_ordering,
-
strategy
-
)
-
else
-
PartialClassDoubleProxy.new(self, object, @expectation_ordering)
-
end
-
end
-
-
1
def any_instance_recorder_not_found_for(id, klass)
-
any_instance_recorders[id] = AnyInstance::Recorder.new(klass)
-
end
-
-
1
if defined?(::BasicObject) && !::BasicObject.method_defined?(:__id__) # for 1.9.2
-
require 'securerandom'
-
-
def id_for(object)
-
id = object.__id__
-
-
return id if object.equal?(::ObjectSpace._id2ref(id))
-
# this suggests that object.__id__ is proxying through to some wrapped object
-
-
object.instance_exec do
-
@__id_for_rspec_mocks_space ||= ::SecureRandom.uuid
-
end
-
end
-
else
-
1
def id_for(object)
-
object.__id__
-
end
-
end
-
end
-
-
# @private
-
1
class NestedSpace < Space
-
1
def initialize(parent)
-
@parent = parent
-
super()
-
end
-
-
1
def proxies_of(klass)
-
super + @parent.proxies_of(klass)
-
end
-
-
1
def constant_mutator_for(name)
-
super || @parent.constant_mutator_for(name)
-
end
-
-
1
def registered?(object)
-
super || @parent.registered?(object)
-
end
-
-
1
private
-
-
1
def proxy_not_found_for(id, object)
-
@parent.proxies[id] || super
-
end
-
-
1
def any_instance_recorder_not_found_for(id, klass)
-
@parent.any_instance_recorders[id] || super
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# @api private
-
# Provides methods for enabling and disabling the available syntaxes
-
# provided by rspec-mocks.
-
1
module Syntax
-
# @private
-
1
def self.warn_about_should!
-
1
@warn_about_should = true
-
end
-
-
# @private
-
1
def self.warn_unless_should_configured(method_name , replacement="the new `:expect` syntax or explicitly enable `:should`")
-
if @warn_about_should
-
RSpec.deprecate(
-
"Using `#{method_name}` from rspec-mocks' old `:should` syntax without explicitly enabling the syntax",
-
:replacement => replacement
-
)
-
-
@warn_about_should = false
-
end
-
end
-
-
# @api private
-
# Enables the should syntax (`dbl.stub`, `dbl.should_receive`, etc).
-
1
def self.enable_should(syntax_host=default_should_syntax_host)
-
1
@warn_about_should = false if syntax_host == default_should_syntax_host
-
1
return if should_enabled?(syntax_host)
-
-
1
syntax_host.class_exec do
-
1
def should_receive(message, opts={}, &block)
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
::RSpec::Mocks.expect_message(self, message, opts, &block)
-
end
-
-
1
def should_not_receive(message, &block)
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
::RSpec::Mocks.expect_message(self, message, {}, &block).never
-
end
-
-
1
def stub(message_or_hash, opts={}, &block)
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
if ::Hash === message_or_hash
-
message_or_hash.each { |message, value| stub(message).and_return value }
-
else
-
::RSpec::Mocks.allow_message(self, message_or_hash, opts, &block)
-
end
-
end
-
-
1
def unstub(message)
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__, "`allow(...).to receive(...).and_call_original` or explicitly enable `:should`")
-
::RSpec::Mocks.space.proxy_for(self).remove_stub(message)
-
end
-
-
1
def stub_chain(*chain, &blk)
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
::RSpec::Mocks::StubChain.stub_chain_on(self, *chain, &blk)
-
end
-
-
1
def as_null_object
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
@_null_object = true
-
::RSpec::Mocks.space.proxy_for(self).as_null_object
-
end
-
-
1
def null_object?
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
defined?(@_null_object)
-
end
-
-
1
def received_message?(message, *args, &block)
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
::RSpec::Mocks.space.proxy_for(self).received_message?(message, *args, &block)
-
end
-
-
1
unless Class.respond_to? :any_instance
-
1
Class.class_exec do
-
1
def any_instance
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
::RSpec::Mocks.space.any_instance_proxy_for(self)
-
end
-
end
-
end
-
end
-
end
-
-
# @api private
-
# Disables the should syntax (`dbl.stub`, `dbl.should_receive`, etc).
-
1
def self.disable_should(syntax_host=default_should_syntax_host)
-
return unless should_enabled?(syntax_host)
-
-
syntax_host.class_exec do
-
undef should_receive
-
undef should_not_receive
-
undef stub
-
undef unstub
-
undef stub_chain
-
undef as_null_object
-
undef null_object?
-
undef received_message?
-
end
-
-
Class.class_exec do
-
undef any_instance
-
end
-
end
-
-
# @api private
-
# Enables the expect syntax (`expect(dbl).to receive`, `allow(dbl).to receive`, etc).
-
1
def self.enable_expect(syntax_host=::RSpec::Mocks::ExampleMethods)
-
1
return if expect_enabled?(syntax_host)
-
-
1
syntax_host.class_exec do
-
1
def receive(method_name, &block)
-
Matchers::Receive.new(method_name, block)
-
end
-
-
1
def receive_messages(message_return_value_hash)
-
matcher = Matchers::ReceiveMessages.new(message_return_value_hash)
-
matcher.warn_about_block if block_given?
-
matcher
-
end
-
-
1
def receive_message_chain(*messages, &block)
-
Matchers::ReceiveMessageChain.new(messages, &block)
-
end
-
-
1
def allow(target)
-
AllowanceTarget.new(target)
-
end
-
-
1
def expect_any_instance_of(klass)
-
AnyInstanceExpectationTarget.new(klass)
-
end
-
-
1
def allow_any_instance_of(klass)
-
AnyInstanceAllowanceTarget.new(klass)
-
end
-
end
-
-
1
RSpec::Mocks::ExampleMethods::ExpectHost.class_exec do
-
1
def expect(target)
-
ExpectationTarget.new(target)
-
end
-
end
-
end
-
-
# @api private
-
# Disables the expect syntax (`expect(dbl).to receive`, `allow(dbl).to receive`, etc).
-
1
def self.disable_expect(syntax_host=::RSpec::Mocks::ExampleMethods)
-
return unless expect_enabled?(syntax_host)
-
-
syntax_host.class_exec do
-
undef receive
-
undef receive_messages
-
undef receive_message_chain
-
undef allow
-
undef expect_any_instance_of
-
undef allow_any_instance_of
-
end
-
-
RSpec::Mocks::ExampleMethods::ExpectHost.class_exec do
-
undef expect
-
end
-
end
-
-
# @api private
-
# Indicates whether or not the should syntax is enabled.
-
1
def self.should_enabled?(syntax_host=default_should_syntax_host)
-
1
syntax_host.method_defined?(:should_receive)
-
end
-
-
# @api private
-
# Indicates whether or not the expect syntax is enabled.
-
1
def self.expect_enabled?(syntax_host=::RSpec::Mocks::ExampleMethods)
-
1
syntax_host.method_defined?(:allow)
-
end
-
-
# @api private
-
# Determines where the methods like `should_receive`, and `stub` are added.
-
1
def self.default_should_syntax_host
-
# JRuby 1.7.4 introduces a regression whereby `defined?(::BasicObject) => nil`
-
# yet `BasicObject` still exists and patching onto ::Object breaks things
-
# e.g. SimpleDelegator expectations won't work
-
#
-
# See: https://github.com/jruby/jruby/issues/814
-
2
if defined?(JRUBY_VERSION) && JRUBY_VERSION == '1.7.4' && RUBY_VERSION.to_f > 1.8
-
return ::BasicObject
-
end
-
-
# On 1.8.7, Object.ancestors.last == Kernel but
-
# things blow up if we include `RSpec::Mocks::Methods`
-
# into Kernel...not sure why.
-
2
return Object unless defined?(::BasicObject)
-
-
# MacRuby has BasicObject but it's not the root class.
-
2
return Object unless Object.ancestors.last == ::BasicObject
-
-
2
::BasicObject
-
end
-
end
-
end
-
end
-
-
1
if defined?(BasicObject)
-
# The legacy `:should` syntax adds the following methods directly to
-
# `BasicObject` so that they are available off of any object. Note, however,
-
# that this syntax does not always play nice with delegate/proxy objects.
-
# We recommend you use the non-monkeypatching `:expect` syntax instead.
-
# @see Class
-
1
class BasicObject
-
# @method should_receive
-
# Sets an expectation that this object should receive a message before
-
# the end of the example.
-
#
-
# @example
-
# logger = double('logger')
-
# thing_that_logs = ThingThatLogs.new(logger)
-
# logger.should_receive(:log)
-
# thing_that_logs.do_something_that_logs_a_message
-
#
-
# @note This is only available when you have enabled the `should` syntax.
-
# @see RSpec::Mocks::ExampleMethods#expect
-
-
# @method should_not_receive
-
# Sets and expectation that this object should _not_ receive a message
-
# during this example.
-
# @see RSpec::Mocks::ExampleMethods#expect
-
-
# @method stub
-
# Tells the object to respond to the message with the specified value.
-
#
-
# @example
-
# counter.stub(:count).and_return(37)
-
# counter.stub(:count => 37)
-
# counter.stub(:count) { 37 }
-
#
-
# @note This is only available when you have enabled the `should` syntax.
-
# @see RSpec::Mocks::ExampleMethods#allow
-
-
# @method unstub
-
# Removes a stub. On a double, the object will no longer respond to
-
# `message`. On a real object, the original method (if it exists) is
-
# restored.
-
#
-
# This is rarely used, but can be useful when a stub is set up during a
-
# shared `before` hook for the common case, but you want to replace it
-
# for a special case.
-
#
-
# @note This is only available when you have enabled the `should` syntax.
-
-
# @method stub_chain
-
# @overload stub_chain(method1, method2)
-
# @overload stub_chain("method1.method2")
-
# @overload stub_chain(method1, method_to_value_hash)
-
#
-
# Stubs a chain of methods.
-
#
-
# ## Warning:
-
#
-
# Chains can be arbitrarily long, which makes it quite painless to
-
# violate the Law of Demeter in violent ways, so you should consider any
-
# use of `stub_chain` a code smell. Even though not all code smells
-
# indicate real problems (think fluent interfaces), `stub_chain` still
-
# results in brittle examples. For example, if you write
-
# `foo.stub_chain(:bar, :baz => 37)` in a spec and then the
-
# implementation calls `foo.baz.bar`, the stub will not work.
-
#
-
# @example
-
# double.stub_chain("foo.bar") { :baz }
-
# double.stub_chain(:foo, :bar => :baz)
-
# double.stub_chain(:foo, :bar) { :baz }
-
#
-
# # Given any of ^^ these three forms ^^:
-
# double.foo.bar # => :baz
-
#
-
# # Common use in Rails/ActiveRecord:
-
# Article.stub_chain("recent.published") { [Article.new] }
-
#
-
# @note This is only available when you have enabled the `should` syntax.
-
# @see RSpec::Mocks::ExampleMethods#receive_message_chain
-
-
# @method as_null_object
-
# Tells the object to respond to all messages. If specific stub values
-
# are declared, they'll work as expected. If not, the receiver is
-
# returned.
-
#
-
# @note This is only available when you have enabled the `should` syntax.
-
-
# @method null_object?
-
# Returns true if this object has received `as_null_object`
-
#
-
# @note This is only available when you have enabled the `should` syntax.
-
end
-
end
-
-
# The legacy `:should` syntax adds the `any_instance` to `Class`.
-
# We generally recommend you use the newer `:expect` syntax instead,
-
# which allows you to stub any instance of a class using
-
# `allow_any_instance_of(klass)` or mock any instance using
-
# `expect_any_instance_of(klass)`.
-
# @see BasicObject
-
1
class Class
-
# @method any_instance
-
# Used to set stubs and message expectations on any instance of a given
-
# class. Returns a [Recorder](Recorder), which records messages like
-
# `stub` and `should_receive` for later playback on instances of the
-
# class.
-
#
-
# @example
-
# Car.any_instance.should_receive(:go)
-
# race = Race.new
-
# race.cars << Car.new
-
# race.go # assuming this delegates to all of its cars
-
# # this example would pass
-
#
-
# Account.any_instance.stub(:balance) { Money.new(:USD, 25) }
-
# Account.new.balance # => Money.new(:USD, 25))
-
#
-
# @return [Recorder]
-
#
-
# @note This is only available when you have enabled the `should` syntax.
-
# @see RSpec::Mocks::ExampleMethods#expect_any_instance_of
-
# @see RSpec::Mocks::ExampleMethods#allow_any_instance_of
-
end
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
class TargetBase
-
1
def initialize(target)
-
@target = target
-
end
-
-
1
def self.delegate_to(matcher_method)
-
4
define_method(:to) do |matcher, &block|
-
unless matcher_allowed?(matcher)
-
raise_unsupported_matcher(:to, matcher)
-
end
-
define_matcher(matcher, matcher_method, &block)
-
end
-
end
-
-
1
def self.delegate_not_to(matcher_method, options={})
-
4
method_name = options.fetch(:from)
-
4
define_method(method_name) do |matcher, &block|
-
case matcher
-
when Matchers::Receive, Matchers::HaveReceived
-
define_matcher(matcher, matcher_method, &block)
-
when Matchers::ReceiveMessages, Matchers::ReceiveMessageChain
-
raise_negation_unsupported(method_name, matcher)
-
else
-
raise_unsupported_matcher(method_name, matcher)
-
end
-
end
-
end
-
-
1
def self.disallow_negation(method_name)
-
4
define_method(method_name) do |matcher, *_args|
-
raise_negation_unsupported(method_name, matcher)
-
end
-
end
-
-
1
private
-
-
1
def matcher_allowed?(matcher)
-
matcher.class.name.start_with?("RSpec::Mocks::Matchers".freeze)
-
end
-
-
1
def define_matcher(matcher, name, &block)
-
matcher.__send__(name, @target, &block)
-
end
-
-
1
def raise_unsupported_matcher(method_name, matcher)
-
raise UnsupportedMatcherError,
-
"only the `receive`, `have_received` and `receive_messages` matchers are supported " \
-
"with `#{expression}(...).#{method_name}`, but you have provided: #{matcher}"
-
end
-
-
1
def raise_negation_unsupported(method_name, matcher)
-
raise NegationUnsupportedError,
-
"`#{expression}(...).#{method_name} #{matcher.name}` is not supported since it " \
-
"doesn't really make sense. What would it even mean?"
-
end
-
-
1
def expression
-
self.class::EXPRESSION
-
end
-
end
-
-
# @private
-
1
class AllowanceTarget < TargetBase
-
1
EXPRESSION = :allow
-
1
delegate_to :setup_allowance
-
1
disallow_negation :not_to
-
1
disallow_negation :to_not
-
end
-
-
# @private
-
1
class ExpectationTarget < TargetBase
-
1
EXPRESSION = :expect
-
1
delegate_to :setup_expectation
-
1
delegate_not_to :setup_negative_expectation, :from => :not_to
-
1
delegate_not_to :setup_negative_expectation, :from => :to_not
-
end
-
-
# @private
-
1
class AnyInstanceAllowanceTarget < TargetBase
-
1
EXPRESSION = :allow_any_instance_of
-
1
delegate_to :setup_any_instance_allowance
-
1
disallow_negation :not_to
-
1
disallow_negation :to_not
-
end
-
-
# @private
-
1
class AnyInstanceExpectationTarget < TargetBase
-
1
EXPRESSION = :expect_any_instance_of
-
1
delegate_to :setup_any_instance_expectation
-
1
delegate_not_to :setup_any_instance_negative_expectation, :from => :not_to
-
1
delegate_not_to :setup_any_instance_negative_expectation, :from => :to_not
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# Implements the methods needed for a pure test double. RSpec::Mocks::Double
-
# includes this module, and it is provided for cases where you want a
-
# pure test double without subclassing RSpec::Mocks::Double.
-
1
module TestDouble
-
# Creates a new test double with a `name` (that will be used in error
-
# messages only)
-
1
def initialize(name=nil, stubs={})
-
@__expired = false
-
if Hash === name && stubs.empty?
-
stubs = name
-
@name = nil
-
else
-
@name = name
-
end
-
assign_stubs(stubs)
-
end
-
-
# Tells the object to respond to all messages. If specific stub values
-
# are declared, they'll work as expected. If not, the receiver is
-
# returned.
-
1
def as_null_object
-
__mock_proxy.as_null_object
-
end
-
-
# Returns true if this object has received `as_null_object`
-
1
def null_object?
-
__mock_proxy.null_object?
-
end
-
-
# This allows for comparing the mock to other objects that proxy such as
-
# ActiveRecords belongs_to proxy objects. By making the other object run
-
# the comparison, we're sure the call gets delegated to the proxy
-
# target.
-
1
def ==(other)
-
other == __mock_proxy
-
end
-
-
# @private
-
1
def inspect
-
TestDoubleFormatter.format(self)
-
end
-
-
# @private
-
1
def to_s
-
inspect.gsub('<', '[').gsub('>', ']')
-
end
-
-
# @private
-
1
def respond_to?(message, incl_private=false)
-
__mock_proxy.null_object? ? true : super
-
end
-
-
# @private
-
1
def __build_mock_proxy_unless_expired(order_group)
-
__raise_expired_error || __build_mock_proxy(order_group)
-
end
-
-
# @private
-
1
def __disallow_further_usage!
-
@__expired = true
-
end
-
-
# Override for default freeze implementation to prevent freezing of test
-
# doubles.
-
1
def freeze
-
RSpec.warn_with("WARNING: you attempted to freeze a test double. This is explicitly a no-op as freezing doubles can lead to undesired behaviour when resetting tests.")
-
end
-
-
1
private
-
-
1
def method_missing(message, *args, &block)
-
proxy = __mock_proxy
-
proxy.record_message_received(message, *args, &block)
-
-
if proxy.null_object?
-
case message
-
when :to_int then return 0
-
when :to_a, :to_ary then return nil
-
when :to_str then return to_s
-
else return self
-
end
-
end
-
-
# Defined private and protected methods will still trigger `method_missing`
-
# when called publicly. We want ruby's method visibility error to get raised,
-
# so we simply delegate to `super` in that case.
-
# ...well, we would delegate to `super`, but there's a JRuby
-
# bug, so we raise our own visibility error instead:
-
# https://github.com/jruby/jruby/issues/1398
-
visibility = proxy.visibility_for(message)
-
if visibility == :private || visibility == :protected
-
ErrorGenerator.new(self).raise_non_public_error(
-
message, visibility
-
)
-
end
-
-
# Required wrapping doubles in an Array on Ruby 1.9.2
-
raise NoMethodError if [:to_a, :to_ary].include? message
-
proxy.raise_unexpected_message_error(message, args)
-
end
-
-
1
def assign_stubs(stubs)
-
stubs.each_pair do |message, response|
-
__mock_proxy.add_simple_stub(message, response)
-
end
-
end
-
-
1
def __mock_proxy
-
::RSpec::Mocks.space.proxy_for(self)
-
end
-
-
1
def __build_mock_proxy(order_group)
-
TestDoubleProxy.new(self, order_group)
-
end
-
-
1
def __raise_expired_error
-
return false unless @__expired
-
ErrorGenerator.new(self).raise_expired_test_double_error
-
end
-
-
1
def initialize_copy(other)
-
as_null_object if other.null_object?
-
super
-
end
-
end
-
-
# A generic test double object. `double`, `instance_double` and friends
-
# return an instance of this.
-
1
class Double
-
1
include TestDouble
-
end
-
-
# @private
-
1
module TestDoubleFormatter
-
1
def self.format(dbl, unwrap=false)
-
format = "#{type_desc(dbl)}#{verified_module_desc(dbl)} #{name_desc(dbl)}"
-
return format if unwrap
-
"#<#{format}>"
-
end
-
-
1
class << self
-
1
private
-
-
1
def type_desc(dbl)
-
case dbl
-
when InstanceVerifyingDouble then "InstanceDouble"
-
when ClassVerifyingDouble then "ClassDouble"
-
when ObjectVerifyingDouble then "ObjectDouble"
-
else "Double"
-
end
-
end
-
-
# @private
-
1
IVAR_GET = Object.instance_method(:instance_variable_get)
-
-
1
def verified_module_desc(dbl)
-
return nil unless VerifyingDouble === dbl
-
"(#{IVAR_GET.bind(dbl).call(:@doubled_module).description})"
-
end
-
-
1
def name_desc(dbl)
-
return "(anonymous)" unless (name = IVAR_GET.bind(dbl).call(:@name))
-
name.inspect
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_mocks 'verifying_proxy'
-
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
module VerifyingDouble
-
1
def respond_to?(message, include_private=false)
-
return super unless null_object?
-
-
method_ref = __mock_proxy.method_reference[message]
-
-
case method_ref.visibility
-
when :public then true
-
when :private then include_private
-
when :protected then include_private || RUBY_VERSION.to_f < 2.0
-
else !method_ref.unimplemented?
-
end
-
end
-
-
1
def method_missing(message, *args, &block)
-
# Null object conditional is an optimization. If not a null object,
-
# validity of method expectations will have been checked at definition
-
# time.
-
if null_object?
-
if @__sending_message == message
-
__mock_proxy.ensure_implemented(message)
-
else
-
__mock_proxy.ensure_publicly_implemented(message, self)
-
end
-
-
__mock_proxy.validate_arguments!(message, args)
-
end
-
-
super
-
end
-
-
# @private
-
1
module SilentIO
-
1
def self.method_missing(*); end
-
1
def self.respond_to?(*)
-
1
true
-
end
-
end
-
-
# Redefining `__send__` causes ruby to issue a warning.
-
1
old, $stderr = $stderr, SilentIO
-
1
def __send__(name, *args, &block)
-
@__sending_message = name
-
super
-
ensure
-
@__sending_message = nil
-
end
-
1
$stderr = old
-
-
1
def send(name, *args, &block)
-
__send__(name, *args, &block)
-
end
-
-
1
def initialize(doubled_module, *args)
-
@doubled_module = doubled_module
-
-
possible_name = args.first
-
name = if String === possible_name || Symbol === possible_name
-
args.shift
-
end
-
-
super(name, *args)
-
@__sending_message = nil
-
end
-
end
-
-
# A mock providing a custom proxy that can verify the validity of any
-
# method stubs or expectations against the public instance methods of the
-
# given class.
-
#
-
# @private
-
1
class InstanceVerifyingDouble
-
1
include TestDouble
-
1
include VerifyingDouble
-
-
1
def __build_mock_proxy(order_group)
-
VerifyingProxy.new(self, order_group,
-
@doubled_module,
-
InstanceMethodReference
-
)
-
end
-
end
-
-
# An awkward module necessary because we cannot otherwise have
-
# ClassVerifyingDouble inherit from Module and still share these methods.
-
#
-
# @private
-
1
module ObjectVerifyingDoubleMethods
-
1
include TestDouble
-
1
include VerifyingDouble
-
-
1
def as_stubbed_const(options={})
-
ConstantMutator.stub(@doubled_module.const_to_replace, self, options)
-
self
-
end
-
-
1
private
-
-
1
def __build_mock_proxy(order_group)
-
VerifyingProxy.new(self, order_group,
-
@doubled_module,
-
ObjectMethodReference
-
)
-
end
-
end
-
-
# Similar to an InstanceVerifyingDouble, except that it verifies against
-
# public methods of the given object.
-
#
-
# @private
-
1
class ObjectVerifyingDouble
-
1
include ObjectVerifyingDoubleMethods
-
end
-
-
# Effectively the same as an ObjectVerifyingDouble (since a class is a type
-
# of object), except with Module in the inheritance chain so that
-
# transferring nested constants to work.
-
#
-
# @private
-
1
class ClassVerifyingDouble < Module
-
1
include ObjectVerifyingDoubleMethods
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_support 'method_signature_verifier'
-
-
1
module RSpec
-
1
module Mocks
-
# A message expectation that knows about the real implementation of the
-
# message being expected, so that it can verify that any expectations
-
# have the valid arguments.
-
# @api private
-
1
class VerifyingMessageExpectation < MessageExpectation
-
# A level of indirection is used here rather than just passing in the
-
# method itself, since method look up is expensive and we only want to
-
# do it if actually needed.
-
#
-
# Conceptually the method reference makes more sense as a constructor
-
# argument since it should be immutable, but it is significantly more
-
# straight forward to build the object in pieces so for now it stays as
-
# an accessor.
-
1
attr_accessor :method_reference
-
-
1
def initialize(*args)
-
super
-
end
-
-
# @private
-
1
def with(*args, &block)
-
super(*args, &block).tap do
-
validate_expected_arguments! do |signature|
-
example_call_site_args = [:an_arg] * signature.min_non_kw_args
-
example_call_site_args << :kw_args_hash if signature.required_kw_args.any?
-
@argument_list_matcher.resolve_expected_args_based_on(example_call_site_args)
-
end
-
end
-
end
-
-
1
private
-
-
1
def validate_expected_arguments!
-
return if method_reference.nil?
-
-
method_reference.with_signature do |signature|
-
args = yield signature
-
verifier = Support::LooseSignatureVerifier.new(signature, args)
-
-
unless verifier.valid?
-
# Fail fast is required, otherwise the message expectation will fail
-
# as well ("expected method not called") and clobber this one.
-
@failed_fast = true
-
@error_generator.raise_invalid_arguments_error(verifier)
-
end
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_mocks 'verifying_message_expectation'
-
1
RSpec::Support.require_rspec_mocks 'method_reference'
-
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
class CallbackInvocationStrategy
-
1
def call(doubled_module)
-
RSpec::Mocks.configuration.verifying_double_callbacks.each do |block|
-
block.call doubled_module
-
end
-
end
-
end
-
-
# @private
-
1
class NoCallbackInvocationStrategy
-
1
def call(_doubled_module)
-
end
-
end
-
-
# @private
-
1
module VerifyingProxyMethods
-
1
def add_stub(method_name, opts={}, &implementation)
-
ensure_implemented(method_name)
-
super
-
end
-
-
1
def add_simple_stub(method_name, *args)
-
ensure_implemented(method_name)
-
super
-
end
-
-
1
def add_message_expectation(method_name, opts={}, &block)
-
ensure_implemented(method_name)
-
super
-
end
-
-
1
def ensure_implemented(method_name)
-
return unless method_reference[method_name].unimplemented?
-
-
@error_generator.raise_unimplemented_error(
-
@doubled_module,
-
method_name,
-
@object
-
)
-
end
-
-
1
def ensure_publicly_implemented(method_name, _object)
-
ensure_implemented(method_name)
-
visibility = method_reference[method_name].visibility
-
-
return if visibility == :public
-
@error_generator.raise_non_public_error(method_name, visibility)
-
end
-
end
-
-
# A verifying proxy mostly acts like a normal proxy, except that it
-
# contains extra logic to try and determine the validity of any expectation
-
# set on it. This includes whether or not methods have been defined and the
-
# validatiy of arguments on method calls.
-
#
-
# In all other ways this behaves like a normal proxy. It only adds the
-
# verification behaviour to specific methods then delegates to the parent
-
# implementation.
-
#
-
# These checks are only activated if the doubled class has already been
-
# loaded, otherwise they are disabled. This allows for testing in
-
# isolation.
-
#
-
# @private
-
1
class VerifyingProxy < TestDoubleProxy
-
1
include VerifyingProxyMethods
-
-
1
def initialize(object, order_group, doubled_module, method_reference_class)
-
super(object, order_group)
-
@object = object
-
@doubled_module = doubled_module
-
@method_reference_class = method_reference_class
-
-
# A custom method double is required to pass through a way to lookup
-
# methods to determine their parameters. This is only relevant if the doubled
-
# class is loaded.
-
@method_doubles = Hash.new do |h, k|
-
h[k] = VerifyingMethodDouble.new(@object, k, self, method_reference[k])
-
end
-
end
-
-
1
def method_reference
-
@method_reference ||= Hash.new do |h, k|
-
h[k] = @method_reference_class.for(@doubled_module, k)
-
end
-
end
-
-
1
def visibility_for(method_name)
-
method_reference[method_name].visibility
-
end
-
-
1
def validate_arguments!(method_name, args)
-
@method_doubles[method_name].validate_arguments!(args)
-
end
-
end
-
-
# @private
-
1
DEFAULT_CALLBACK_INVOCATION_STRATEGY = CallbackInvocationStrategy.new
-
-
# @private
-
1
class VerifyingPartialDoubleProxy < PartialDoubleProxy
-
1
include VerifyingProxyMethods
-
-
1
def initialize(object, expectation_ordering, optional_callback_invocation_strategy=DEFAULT_CALLBACK_INVOCATION_STRATEGY)
-
super(object, expectation_ordering)
-
@doubled_module = DirectObjectReference.new(object)
-
-
# A custom method double is required to pass through a way to lookup
-
# methods to determine their parameters.
-
@method_doubles = Hash.new do |h, k|
-
h[k] = VerifyingExistingMethodDouble.for(object, k, self)
-
end
-
-
optional_callback_invocation_strategy.call(@doubled_module)
-
end
-
-
1
def method_reference
-
@method_doubles
-
end
-
end
-
-
# @private
-
1
class VerifyingPartialClassDoubleProxy < VerifyingPartialDoubleProxy
-
1
include PartialClassDoubleProxyMethods
-
end
-
-
# @private
-
1
class VerifyingMethodDouble < MethodDouble
-
1
def initialize(object, method_name, proxy, method_reference)
-
super(object, method_name, proxy)
-
@method_reference = method_reference
-
end
-
-
1
def message_expectation_class
-
VerifyingMessageExpectation
-
end
-
-
1
def add_expectation(*args, &block)
-
# explict params necessary for 1.8.7 see #626
-
super(*args, &block).tap { |x| x.method_reference = @method_reference }
-
end
-
-
1
def add_stub(*args, &block)
-
# explict params necessary for 1.8.7 see #626
-
super(*args, &block).tap { |x| x.method_reference = @method_reference }
-
end
-
-
1
def proxy_method_invoked(obj, *args, &block)
-
validate_arguments!(args)
-
super
-
end
-
-
1
def validate_arguments!(actual_args)
-
@method_reference.with_signature do |signature|
-
verifier = Support::StrictSignatureVerifier.new(signature, actual_args)
-
raise ArgumentError, verifier.error_message unless verifier.valid?
-
end
-
end
-
end
-
-
# A VerifyingMethodDouble fetches the method to verify against from the
-
# original object, using a MethodReference. This works for pure doubles,
-
# but when the original object is itself the one being modified we need to
-
# collapse the reference and the method double into a single object so that
-
# we can access the original pristine method definition.
-
#
-
# @private
-
1
class VerifyingExistingMethodDouble < VerifyingMethodDouble
-
1
def initialize(object, method_name, proxy)
-
super(object, method_name, proxy, self)
-
-
@valid_method = object.respond_to?(method_name, true)
-
-
# Trigger an eager find of the original method since if we find it any
-
# later we end up getting a stubbed method with incorrect arity.
-
save_original_implementation_callable!
-
end
-
-
1
def with_signature
-
yield Support::MethodSignature.new(original_implementation_callable)
-
end
-
-
1
def unimplemented?
-
!@valid_method
-
end
-
-
1
def self.for(object, method_name, proxy)
-
if ClassNewMethodReference.applies_to?(method_name) { object }
-
VerifyingExistingClassNewMethodDouble
-
else
-
self
-
end.new(object, method_name, proxy)
-
end
-
end
-
-
# Used in place of a `VerifyingExistingMethodDouble` for the specific case
-
# of mocking or stubbing a `new` method on a class. In this case, we substitute
-
# the method signature from `#initialize` since new's signature is just `*args`.
-
#
-
# @private
-
1
class VerifyingExistingClassNewMethodDouble < VerifyingExistingMethodDouble
-
1
def with_signature
-
yield Support::MethodSignature.new(object.instance_method(:initialize))
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# Version information for RSpec mocks.
-
1
module Version
-
# Version of RSpec mocks currently in use in SemVer format.
-
1
STRING = '3.4.1'
-
end
-
end
-
end
-
1
require 'rspec/core'
-
1
require 'rails/version'
-
-
# Load any of our adapters and extensions early in the process
-
1
require 'rspec/rails/adapters'
-
1
require 'rspec/rails/extensions'
-
-
# Load the rspec-rails parts
-
1
require 'rspec/rails/view_rendering'
-
1
require 'rspec/rails/matchers'
-
1
require 'rspec/rails/fixture_support'
-
1
require 'rspec/rails/example'
-
1
require 'rspec/rails/vendor/capybara'
-
1
require 'rspec/rails/configuration'
-
1
require 'rspec/rails/active_record'
-
1
require 'rspec/rails/feature_check'
-
1
module RSpec
-
1
module Rails
-
# Fake class to document RSpec ActiveRecord configuration options. In practice,
-
# these are dynamically added to the normal RSpec configuration object.
-
1
class ActiveRecordConfiguration
-
# @private
-
1
def self.initialize_activerecord_configuration(config)
-
1
config.before :suite do
-
# This allows dynamic columns etc to be used on ActiveRecord models when creating instance_doubles
-
1
if defined?(ActiveRecord) && defined?(::RSpec::Mocks)
-
1
::RSpec::Mocks.configuration.when_declaring_verifying_double do |possible_model|
-
target = possible_model.target
-
-
if Class === target && ActiveRecord::Base > target && !target.abstract_class?
-
target.define_attribute_methods
-
end
-
end
-
end
-
end
-
end
-
-
1
initialize_activerecord_configuration RSpec.configuration
-
end
-
end
-
end
-
1
require 'delegate'
-
1
require 'active_support'
-
1
require 'active_support/concern'
-
1
require 'active_support/core_ext/string'
-
-
1
module RSpec
-
1
module Rails
-
# @private
-
1
def self.disable_testunit_autorun
-
# `Test::Unit::AutoRunner.need_auto_run=` was introduced to the test-unit
-
# gem in version 2.4.9. Previous to this version `Test::Unit.run=` was
-
# used. The implementation of test-unit included with Ruby has neither
-
# method.
-
if defined?(Test::Unit::AutoRunner.need_auto_run = ())
-
Test::Unit::AutoRunner.need_auto_run = false
-
elsif defined?(Test::Unit.run = ())
-
Test::Unit.run = false
-
end
-
end
-
1
private_class_method :disable_testunit_autorun
-
-
1
if ::Rails::VERSION::STRING >= '4.1.0'
-
1
if defined?(Kernel.gem)
-
1
gem 'minitest'
-
else
-
require 'minitest'
-
end
-
1
require 'minitest/assertions'
-
# Constant aliased to either Minitest or TestUnit, depending on what is
-
# loaded.
-
1
Assertions = Minitest::Assertions
-
elsif RUBY_VERSION >= '2.2.0'
-
# Minitest / TestUnit has been removed from ruby core. However, we are
-
# on an old Rails version and must load the appropriate gem
-
if ::Rails::VERSION::STRING >= '4.0.0'
-
# ActiveSupport 4.0.x has the minitest '~> 4.2' gem as a dependency
-
# This gem has no `lib/minitest.rb` file.
-
gem 'minitest' if defined?(Kernel.gem)
-
require 'minitest/unit'
-
Assertions = MiniTest::Assertions
-
elsif ::Rails::VERSION::STRING >= '3.2.21'
-
# TODO: Change the above check to >= '3.2.22' once it's released
-
begin
-
# Test::Unit "helpfully" sets up autoload for its `AutoRunner`.
-
# While we do not reference it directly, when we load the `TestCase`
-
# classes from AS (ActiveSupport), AS kindly references `AutoRunner`
-
# for everyone.
-
#
-
# To handle this we need to pre-emptively load 'test/unit' and make
-
# sure the version installed has `AutoRunner` (the 3.x line does to
-
# date). If so, we turn the auto runner off.
-
require 'test/unit'
-
require 'test/unit/assertions'
-
disable_testunit_autorun
-
rescue LoadError => e
-
raise LoadError, <<-ERR.squish, e.backtrace
-
Ruby 2.2+ has removed test/unit from the core library. Rails
-
requires this as a dependency. Please add test-unit gem to your
-
Gemfile: `gem 'test-unit', '~> 3.0'` (#{e.message})"
-
ERR
-
end
-
Assertions = Test::Unit::Assertions
-
else
-
abort <<-MSG.squish
-
Ruby 2.2+ is not supported on Rails #{::Rails::VERSION::STRING}.
-
Check the Rails release notes for the appropriate update with
-
support.
-
MSG
-
end
-
else
-
begin
-
require 'test/unit/assertions'
-
rescue LoadError
-
# work around for Rubinius not having a std std-lib
-
require 'rubysl-test-unit' if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
-
require 'test/unit/assertions'
-
end
-
# Turn off test unit's auto runner for those using the gem
-
disable_testunit_autorun
-
# Constant aliased to either Minitest or TestUnit, depending on what is
-
# loaded.
-
Assertions = Test::Unit::Assertions
-
end
-
-
# @private
-
1
class AssertionDelegator < Module
-
1
def initialize(*assertion_modules)
-
2
assertion_class = Class.new(SimpleDelegator) do
-
2
include ::RSpec::Rails::Assertions
-
2
include ::RSpec::Rails::MinitestCounters
-
4
assertion_modules.each { |mod| include mod }
-
end
-
-
2
super() do
-
2
define_method :build_assertion_instance do
-
18
assertion_class.new(self)
-
end
-
-
2
def assertion_instance
-
18
@assertion_instance ||= build_assertion_instance
-
end
-
-
2
assertion_modules.each do |mod|
-
2
mod.public_instance_methods.each do |method|
-
10
next if method == :method_missing || method == "method_missing"
-
8
define_method(method.to_sym) do |*args, &block|
-
assertion_instance.send(method.to_sym, *args, &block)
-
end
-
end
-
end
-
end
-
end
-
end
-
-
# Adapts example groups for `Minitest::Test::LifecycleHooks`
-
#
-
# @private
-
1
module MinitestLifecycleAdapter
-
1
extend ActiveSupport::Concern
-
-
1
included do |group|
-
34
group.before { after_setup }
-
34
group.after { before_teardown }
-
-
6
group.around do |example|
-
28
before_setup
-
28
example.run
-
28
after_teardown
-
end
-
end
-
-
1
def before_setup
-
end
-
-
1
def after_setup
-
end
-
-
1
def before_teardown
-
end
-
-
1
def after_teardown
-
end
-
end
-
-
# @private
-
1
module MinitestCounters
-
1
attr_writer :assertions
-
1
def assertions
-
8
@assertions ||= 0
-
end
-
end
-
-
# @private
-
1
module SetupAndTeardownAdapter
-
1
extend ActiveSupport::Concern
-
-
1
module ClassMethods
-
# Wraps `setup` calls from within Rails' testing framework in `before`
-
# hooks.
-
1
def setup(*methods)
-
8
methods.each do |method|
-
8
if method.to_s =~ /^setup_(with_controller|fixtures|controller_request_and_response)$/
-
22
prepend_before { __send__ method }
-
else
-
22
before { __send__ method }
-
end
-
end
-
end
-
-
# @api private
-
#
-
# Wraps `teardown` calls from within Rails' testing framework in
-
# `after` hooks.
-
1
def teardown(*methods)
-
26
methods.each { |method| after { __send__ method } }
-
end
-
end
-
-
1
def initialize(*args)
-
78
super
-
78
@example = nil
-
end
-
-
1
def method_name
-
56
@example
-
end
-
end
-
-
# @private
-
1
module MinitestAssertionAdapter
-
1
extend ActiveSupport::Concern
-
-
# @private
-
1
module ClassMethods
-
# Returns the names of assertion methods that we want to expose to
-
# examples without exposing non-assertion methods in Test::Unit or
-
# Minitest.
-
1
def assertion_method_names
-
6
methods = ::RSpec::Rails::Assertions.
-
public_instance_methods.
-
select do |m|
-
270
m.to_s =~ /^(assert|flunk|refute)/
-
end
-
6
methods + [:build_message]
-
end
-
-
1
def define_assertion_delegators
-
6
assertion_method_names.each do |m|
-
210
define_method(m.to_sym) do |*args, &block|
-
8
assertion_delegator.send(m.to_sym, *args, &block)
-
end
-
end
-
end
-
end
-
-
1
class AssertionDelegator
-
1
include ::RSpec::Rails::Assertions
-
1
include ::RSpec::Rails::MinitestCounters
-
end
-
-
1
def assertion_delegator
-
8
@assertion_delegator ||= AssertionDelegator.new
-
end
-
-
1
included do
-
6
define_assertion_delegators
-
end
-
end
-
-
# Backwards compatibility. It's unlikely that anyone is using this
-
# constant, but we had forgotten to mark it as `@private` earlier
-
#
-
# @private
-
1
TestUnitAssertionAdapter = MinitestAssertionAdapter
-
end
-
end
-
1
module RSpec
-
1
module Rails
-
# Fake class to document RSpec Rails configuration options. In practice,
-
# these are dynamically added to the normal RSpec configuration object.
-
1
class Configuration
-
# @!method infer_spec_type_from_file_location!
-
# Automatically tag specs in conventional directories with matching `type`
-
# metadata so that they have relevant helpers available to them. See
-
# `RSpec::Rails::DIRECTORY_MAPPINGS` for details on which metadata is
-
# applied to each directory.
-
-
# @!method render_views=(val)
-
#
-
# When set to `true`, controller specs will render the relevant view as
-
# well. Defaults to `false`.
-
-
# @!method render_views(val)
-
# Enables view rendering for controllers specs.
-
-
# @!method render_views?
-
# Reader for currently value of `render_views` setting.
-
end
-
-
# Mappings used by `infer_spec_type_from_file_location!`.
-
#
-
# @api private
-
1
DIRECTORY_MAPPINGS = {
-
:controller => %w[spec controllers],
-
:helper => %w[spec helpers],
-
:job => %w[spec jobs],
-
:mailer => %w[spec mailers],
-
:model => %w[spec models],
-
:request => %w[spec (requests|integration|api)],
-
:routing => %w[spec routing],
-
:view => %w[spec views],
-
:feature => %w[spec features]
-
}
-
-
# @private
-
1
def self.initialize_configuration(config)
-
1
config.backtrace_exclusion_patterns << /vendor\//
-
1
config.backtrace_exclusion_patterns << %r{lib/rspec/rails}
-
-
# controller settings
-
1
config.add_setting :infer_base_class_for_anonymous_controllers, :default => true
-
-
# fixture support
-
1
config.add_setting :use_transactional_fixtures, :alias_with => :use_transactional_examples
-
1
config.add_setting :use_instantiated_fixtures
-
1
config.add_setting :global_fixtures
-
1
config.add_setting :fixture_path
-
1
config.include RSpec::Rails::FixtureSupport, :use_fixtures
-
-
# We'll need to create a deprecated module in order to properly report to
-
# gems / projects which are relying on this being loaded globally.
-
#
-
# See rspec/rspec-rails#1355 for history
-
#
-
# @deprecated Include `RSpec::Rails::RailsExampleGroup` or
-
# `RSpec::Rails::FixtureSupport` directly instead
-
1
config.include RSpec::Rails::FixtureSupport
-
-
# This allows us to expose `render_views` as a config option even though it
-
# breaks the convention of other options by using `render_views` as a
-
# command (i.e. `render_views = true`), where it would normally be used
-
# as a getter. This makes it easier for rspec-rails users because we use
-
# `render_views` directly in example groups, so this aligns the two APIs,
-
# but requires this workaround:
-
1
config.add_setting :rendering_views, :default => false
-
-
1
config.instance_exec do
-
1
def render_views=(val)
-
self.rendering_views = val
-
end
-
-
1
def render_views
-
self.rendering_views = true
-
end
-
-
1
def render_views?
-
36
rendering_views
-
end
-
-
1
def infer_spec_type_from_file_location!
-
2
DIRECTORY_MAPPINGS.each do |type, dir_parts|
-
18
escaped_path = Regexp.compile(dir_parts.join('[\\\/]') + '[\\\/]')
-
18
define_derived_metadata(:file_path => escaped_path) do |metadata|
-
126
metadata[:type] ||= type
-
end
-
end
-
end
-
-
# Adds exclusion filters for gems included with Rails
-
1
def filter_rails_from_backtrace!
-
1
filter_gems_from_backtrace "actionmailer", "actionpack", "actionview"
-
1
filter_gems_from_backtrace "activemodel", "activerecord",
-
"activesupport", "activejob"
-
end
-
end
-
-
1
config.include RSpec::Rails::ControllerExampleGroup, :type => :controller
-
1
config.include RSpec::Rails::HelperExampleGroup, :type => :helper
-
1
config.include RSpec::Rails::ModelExampleGroup, :type => :model
-
1
config.include RSpec::Rails::RequestExampleGroup, :type => :request
-
1
config.include RSpec::Rails::RoutingExampleGroup, :type => :routing
-
1
config.include RSpec::Rails::ViewExampleGroup, :type => :view
-
1
config.include RSpec::Rails::FeatureExampleGroup, :type => :feature
-
-
1
if defined?(ActionMailer)
-
1
config.include RSpec::Rails::MailerExampleGroup, :type => :mailer
-
end
-
-
1
if defined?(ActiveJob)
-
1
config.include RSpec::Rails::JobExampleGroup, :type => :job
-
end
-
end
-
-
1
initialize_configuration RSpec.configuration
-
end
-
end
-
1
require 'rspec/rails/example/rails_example_group'
-
1
require 'rspec/rails/example/controller_example_group'
-
1
require 'rspec/rails/example/request_example_group'
-
1
require 'rspec/rails/example/helper_example_group'
-
1
require 'rspec/rails/example/view_example_group'
-
1
require 'rspec/rails/example/mailer_example_group'
-
1
require 'rspec/rails/example/routing_example_group'
-
1
require 'rspec/rails/example/model_example_group'
-
1
require 'rspec/rails/example/job_example_group'
-
1
require 'rspec/rails/example/feature_example_group'
-
1
module RSpec
-
1
module Rails
-
# @private
-
1
ControllerAssertionDelegator = RSpec::Rails::AssertionDelegator.new(
-
ActionDispatch::Assertions::RoutingAssertions
-
)
-
-
# @api public
-
# Container module for controller spec functionality.
-
1
module ControllerExampleGroup
-
1
extend ActiveSupport::Concern
-
1
include RSpec::Rails::RailsExampleGroup
-
1
include ActionController::TestCase::Behavior
-
1
include RSpec::Rails::ViewRendering
-
1
include RSpec::Rails::Matchers::RedirectTo
-
1
include RSpec::Rails::Matchers::RenderTemplate
-
1
include RSpec::Rails::Matchers::RoutingMatchers
-
1
include ControllerAssertionDelegator
-
-
# Class-level DSL for controller specs.
-
1
module ClassMethods
-
# @private
-
1
def controller_class
-
18
described_class
-
end
-
-
# Supports a simple DSL for specifying behavior of ApplicationController.
-
# Creates an anonymous subclass of ApplicationController and evals the
-
# `body` in that context. Also sets up implicit routes for this
-
# controller, that are separate from those defined in "config/routes.rb".
-
#
-
# @note Due to Ruby 1.8 scoping rules in anonymous subclasses, constants
-
# defined in `ApplicationController` must be fully qualified (e.g.
-
# `ApplicationController::AccessDenied`) in the block passed to the
-
# `controller` method. Any instance methods, filters, etc, that are
-
# defined in `ApplicationController`, however, are accessible from
-
# within the block.
-
#
-
# @example
-
# describe ApplicationController do
-
# controller do
-
# def index
-
# raise ApplicationController::AccessDenied
-
# end
-
# end
-
#
-
# describe "handling AccessDenied exceptions" do
-
# it "redirects to the /401.html page" do
-
# get :index
-
# response.should redirect_to("/401.html")
-
# end
-
# end
-
# end
-
#
-
# If you would like to spec a subclass of ApplicationController, call
-
# controller like so:
-
#
-
# controller(ApplicationControllerSubclass) do
-
# # ....
-
# end
-
1
def controller(base_class = nil, &body)
-
if RSpec.configuration.infer_base_class_for_anonymous_controllers?
-
base_class ||= controller_class
-
end
-
base_class ||= defined?(ApplicationController) ? ApplicationController : ActionController::Base
-
-
new_controller_class = Class.new(base_class) do
-
def self.name
-
root_controller = defined?(ApplicationController) ? ApplicationController : ActionController::Base
-
if superclass == root_controller || superclass.abstract?
-
"AnonymousController"
-
else
-
superclass.name
-
end
-
end
-
end
-
new_controller_class.class_exec(&body)
-
(class << self; self; end).__send__(:define_method, :controller_class) { new_controller_class }
-
-
before do
-
@orig_routes = routes
-
resource_name = if @controller.respond_to?(:controller_name)
-
@controller.controller_name.to_sym
-
else
-
:anonymous
-
end
-
resource_path = if @controller.respond_to?(:controller_path)
-
@controller.controller_path
-
else
-
resource_name.to_s
-
end
-
resource_module = resource_path.rpartition('/').first.presence
-
resource_as = 'anonymous_' + resource_path.tr('/', '_')
-
self.routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
-
r.draw do
-
resources resource_name,
-
:as => resource_as,
-
:module => resource_module,
-
:path => resource_path
-
end
-
end
-
end
-
-
after do
-
self.routes = @orig_routes
-
@orig_routes = nil
-
end
-
end
-
-
# Specifies the routeset that will be used for the example group. This
-
# is most useful when testing Rails engines.
-
#
-
# @example
-
# describe MyEngine::PostsController do
-
# routes { MyEngine::Engine.routes }
-
#
-
# # ...
-
# end
-
1
def routes
-
before do
-
self.routes = yield
-
end
-
end
-
end
-
-
# @!attribute [r]
-
# Returns the controller object instance under test.
-
1
attr_reader :controller
-
-
# @!attribute [r]
-
# Returns the Rails routes used for the spec.
-
1
attr_reader :routes
-
-
# @private
-
#
-
# RSpec Rails uses this to make Rails routes easily available to specs.
-
1
def routes=(routes)
-
18
@routes = routes
-
18
assertion_instance.instance_variable_set(:@routes, routes)
-
end
-
-
# @private
-
1
module BypassRescue
-
1
def rescue_with_handler(exception)
-
raise exception
-
end
-
end
-
-
# Extends the controller with a module that overrides
-
# `rescue_with_handler` to raise the exception passed to it. Use this to
-
# specify that an action _should_ raise an exception given appropriate
-
# conditions.
-
#
-
# @example
-
# describe ProfilesController do
-
# it "raises a 403 when a non-admin user tries to view another user's profile" do
-
# profile = create_profile
-
# login_as profile.user
-
#
-
# expect do
-
# bypass_rescue
-
# get :show, :id => profile.id + 1
-
# end.to raise_error(/403 Forbidden/)
-
# end
-
# end
-
1
def bypass_rescue
-
controller.extend(BypassRescue)
-
end
-
-
# If method is a named_route, delegates to the RouteSet associated with
-
# this controller.
-
1
def method_missing(method, *args, &block)
-
4
if route_available?(method)
-
3
controller.send(method, *args, &block)
-
else
-
1
super
-
end
-
end
-
-
1
included do
-
4
subject { controller }
-
-
4
before do
-
18
self.routes = ::Rails.application.routes
-
end
-
-
4
around do |ex|
-
18
previous_allow_forgery_protection_value = ActionController::Base.allow_forgery_protection
-
18
begin
-
18
ActionController::Base.allow_forgery_protection = false
-
18
ex.call
-
ensure
-
18
ActionController::Base.allow_forgery_protection = previous_allow_forgery_protection_value
-
end
-
end
-
end
-
-
1
private
-
-
1
def route_available?(method)
-
4
(defined?(@routes) && route_defined?(routes, method)) ||
-
5
(defined?(@orig_routes) && route_defined?(@orig_routes, method))
-
end
-
-
1
def route_defined?(routes, method)
-
4
return false if routes.nil?
-
-
4
if routes.named_routes.respond_to?(:route_defined?)
-
4
routes.named_routes.route_defined?(method)
-
else
-
routes.named_routes.helpers.include?(method)
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Rails
-
# @api public
-
# Container module for routing spec functionality.
-
1
module FeatureExampleGroup
-
1
extend ActiveSupport::Concern
-
1
include RSpec::Rails::RailsExampleGroup
-
-
# Default host to be used in Rails route helpers if none is specified.
-
1
DEFAULT_HOST = "www.example.com"
-
-
1
included do
-
app = ::Rails.application
-
if app.respond_to?(:routes)
-
include app.routes.url_helpers if app.routes.respond_to?(:url_helpers)
-
include app.routes.mounted_helpers if app.routes.respond_to?(:mounted_helpers)
-
-
if respond_to?(:default_url_options)
-
default_url_options[:host] ||= ::RSpec::Rails::FeatureExampleGroup::DEFAULT_HOST
-
end
-
end
-
end
-
-
# Shim to check for presence of Capybara. Will delegate if present, raise
-
# if not. We assume here that in most cases `visit` will be the first
-
# Capybara method called in a spec.
-
1
def visit(*)
-
if defined?(super)
-
super
-
else
-
raise "Capybara not loaded, please add it to your Gemfile:\n\ngem \"capybara\""
-
end
-
end
-
end
-
end
-
end
-
-
1
unless RSpec.respond_to?(:feature)
-
1
opts = {
-
:capybara_feature => true,
-
:type => :feature,
-
:skip => <<-EOT.squish
-
Feature specs require the Capybara (http://github.com/jnicklas/capybara)
-
gem, version 2.2.0 or later. We recommend version 2.4.0 or later to avoid
-
some deprecation warnings and have support for
-
`config.expose_dsl_globally = false`.
-
EOT
-
}
-
-
# Capybara's monkey patching causes us to have to jump through some hoops
-
1
top_level = self
-
1
main_feature = nil
-
1
if defined?(Capybara) && ::Capybara::VERSION.to_f < 2.4
-
# Capybara 2.2 and 2.3 do not use `alias_example_xyz`
-
opts[:skip] = <<-EOT.squish
-
Capybara < 2.4.0 does not support RSpec's namespace or
-
`config.expose_dsl_globally = false`. Upgrade to Capybara >= 2.4.0.
-
EOT
-
main_feature = top_level.method(:feature) if top_level.respond_to?(:feature)
-
end
-
-
1
RSpec.configure do |c|
-
1
main_feature = nil unless c.expose_dsl_globally?
-
1
c.alias_example_group_to :feature, opts
-
1
c.alias_example_to :scenario
-
1
c.alias_example_to :xscenario
-
end
-
-
# Due to load order issues and `config.expose_dsl_globally?` defaulting to
-
# `true` we need to put Capybara's monkey patch method back. Otherwise,
-
# app upgrades have a high likelyhood of having all feature specs skipped.
-
1
top_level.define_singleton_method(:feature, &main_feature) if main_feature
-
end
-
1
require 'rspec/rails/view_assigns'
-
-
1
module RSpec
-
1
module Rails
-
# @api public
-
# Container module for helper specs.
-
1
module HelperExampleGroup
-
1
extend ActiveSupport::Concern
-
1
include RSpec::Rails::RailsExampleGroup
-
1
include ActionView::TestCase::Behavior
-
1
include RSpec::Rails::ViewAssigns
-
-
# @private
-
1
module ClassMethods
-
1
def determine_default_helper_class(_ignore)
-
described_class
-
end
-
end
-
-
# Returns an instance of ActionView::Base with the helper being specified
-
# mixed in, along with any of the built-in rails helpers.
-
1
def helper
-
_view.tap do |v|
-
v.extend(ApplicationHelper) if defined?(ApplicationHelper)
-
v.assign(view_assigns)
-
end
-
end
-
-
1
private
-
-
1
def _controller_path(example)
-
example.example_group.described_class.to_s.sub(/Helper/, '').underscore
-
end
-
-
1
included do
-
before do |example|
-
controller.controller_path = _controller_path(example)
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Rails
-
# @api public
-
# Container module for job spec functionality. It is only available if
-
# ActiveJob has been loaded before it.
-
1
module JobExampleGroup
-
# This blank module is only necessary for YARD processing. It doesn't
-
# handle the conditional `defined?` check below very well.
-
end
-
end
-
end
-
-
1
if defined?(ActiveJob)
-
1
module RSpec
-
1
module Rails
-
# Container module for job spec functionality.
-
1
module JobExampleGroup
-
1
extend ActiveSupport::Concern
-
1
include RSpec::Rails::RailsExampleGroup
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Rails
-
# @api public
-
# Container module for mailer spec functionality. It is only available if
-
# ActionMailer has been loaded before it.
-
1
module MailerExampleGroup
-
# This blank module is only necessary for YARD processing. It doesn't
-
# handle the conditional `defined?` check below very well.
-
end
-
end
-
end
-
-
1
if defined?(ActionMailer)
-
1
module RSpec
-
1
module Rails
-
# Container module for mailer spec functionality.
-
1
module MailerExampleGroup
-
1
extend ActiveSupport::Concern
-
1
include RSpec::Rails::RailsExampleGroup
-
1
include ActionMailer::TestCase::Behavior
-
-
1
included do
-
include ::Rails.application.routes.url_helpers
-
options = ::Rails.configuration.action_mailer.default_url_options
-
options.each { |key, value| default_url_options[key] = value } if options
-
end
-
-
# Class-level DSL for mailer specs.
-
1
module ClassMethods
-
# Alias for `described_class`.
-
1
def mailer_class
-
described_class
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Rails
-
# @api public
-
# Container class for model spec functionality. Does not provide anything
-
# special over the common RailsExampleGroup currently.
-
1
module ModelExampleGroup
-
1
extend ActiveSupport::Concern
-
1
include RSpec::Rails::RailsExampleGroup
-
end
-
end
-
end
-
# Temporary workaround to resolve circular dependency between rspec-rails' spec
-
# suite and ammeter.
-
1
require 'rspec/rails/matchers'
-
-
1
module RSpec
-
1
module Rails
-
# @api public
-
# Common rails example functionality.
-
1
module RailsExampleGroup
-
1
extend ActiveSupport::Concern
-
1
include RSpec::Rails::SetupAndTeardownAdapter
-
1
include RSpec::Rails::MinitestLifecycleAdapter if ::Rails::VERSION::STRING >= '4'
-
1
include RSpec::Rails::MinitestAssertionAdapter
-
1
include RSpec::Rails::Matchers
-
1
include RSpec::Rails::FixtureSupport
-
end
-
end
-
end
-
1
module RSpec
-
1
module Rails
-
# @api public
-
# Container class for request spec functionality.
-
1
module RequestExampleGroup
-
1
extend ActiveSupport::Concern
-
1
include RSpec::Rails::RailsExampleGroup
-
1
include ActionDispatch::Integration::Runner
-
1
include ActionDispatch::Assertions
-
1
include RSpec::Rails::Matchers::RedirectTo
-
1
include RSpec::Rails::Matchers::RenderTemplate
-
1
include ActionController::TemplateAssertions
-
-
# Delegates to `Rails.application`.
-
1
def app
-
::Rails.application
-
end
-
-
1
included do
-
before do
-
@routes = ::Rails.application.routes
-
end
-
end
-
end
-
end
-
end
-
1
require "action_dispatch/testing/assertions/routing"
-
-
1
module RSpec
-
1
module Rails
-
# @private
-
1
RoutingAssertionDelegator = RSpec::Rails::AssertionDelegator.new(
-
ActionDispatch::Assertions::RoutingAssertions
-
)
-
-
# @api public
-
# Container module for routing spec functionality.
-
1
module RoutingExampleGroup
-
1
extend ActiveSupport::Concern
-
1
include RSpec::Rails::RailsExampleGroup
-
1
include RSpec::Rails::Matchers::RoutingMatchers
-
1
include RSpec::Rails::Matchers::RoutingMatchers::RouteHelpers
-
1
include RSpec::Rails::RoutingAssertionDelegator
-
-
# Class-level DSL for route specs.
-
1
module ClassMethods
-
# Specifies the routeset that will be used for the example group. This
-
# is most useful when testing Rails engines.
-
#
-
# @example
-
# describe MyEngine::PostsController do
-
# routes { MyEngine::Engine.routes }
-
#
-
# it "routes posts#index" do
-
# expect(:get => "/posts").to
-
# route_to(:controller => "my_engine/posts", :action => "index")
-
# end
-
# end
-
1
def routes
-
before do
-
self.routes = yield
-
end
-
end
-
end
-
-
1
included do
-
before do
-
self.routes = ::Rails.application.routes
-
end
-
end
-
-
# @!attribute [r]
-
# @private
-
1
attr_reader :routes
-
-
# @private
-
1
def routes=(routes)
-
@routes = routes
-
assertion_instance.instance_variable_set(:@routes, routes)
-
end
-
-
1
private
-
-
1
def method_missing(m, *args, &block)
-
routes.url_helpers.respond_to?(m) ? routes.url_helpers.send(m, *args) : super
-
end
-
end
-
end
-
end
-
1
require 'rspec/rails/view_assigns'
-
-
1
module RSpec
-
1
module Rails
-
# @api public
-
# Container class for view spec functionality.
-
1
module ViewExampleGroup
-
1
extend ActiveSupport::Concern
-
1
include RSpec::Rails::RailsExampleGroup
-
1
include ActionView::TestCase::Behavior
-
1
include RSpec::Rails::ViewAssigns
-
1
include RSpec::Rails::Matchers::RenderTemplate
-
-
# @private
-
1
module ClassMethods
-
1
def _default_helper
-
base = metadata[:description].split('/')[0..-2].join('/')
-
(base.camelize + 'Helper').constantize unless base.to_s.empty?
-
rescue NameError
-
nil
-
end
-
-
1
def _default_helpers
-
helpers = [_default_helper].compact
-
helpers << ApplicationHelper if Object.const_defined?('ApplicationHelper')
-
helpers
-
end
-
end
-
-
# DSL exposed to view specs.
-
1
module ExampleMethods
-
# @overload render
-
# @overload render({:partial => path_to_file})
-
# @overload render({:partial => path_to_file}, {... locals ...})
-
# @overload render({:partial => path_to_file}, {... locals ...}) do ... end
-
#
-
# Delegates to ActionView::Base#render, so see documentation on that
-
# for more info.
-
#
-
# The only addition is that you can call render with no arguments, and
-
# RSpec will pass the top level description to render:
-
#
-
# describe "widgets/new.html.erb" do
-
# it "shows all the widgets" do
-
# render # => view.render(:file => "widgets/new.html.erb")
-
# # ...
-
# end
-
# end
-
1
def render(options = {}, local_assigns = {}, &block)
-
options = _default_render_options if Hash === options && options.empty?
-
super(options, local_assigns, &block)
-
end
-
-
# The instance of `ActionView::Base` that is used to render the template.
-
# Use this to stub methods _before_ calling `render`.
-
#
-
# describe "widgets/new.html.erb" do
-
# it "shows all the widgets" do
-
# view.stub(:foo) { "foo" }
-
# render
-
# # ...
-
# end
-
# end
-
1
def view
-
_view
-
end
-
-
# Simulates the presence of a template on the file system by adding a
-
# Rails' FixtureResolver to the front of the view_paths list. Designed to
-
# help isolate view examples from partials rendered by the view template
-
# that is the subject of the example.
-
#
-
# stub_template("widgets/_widget.html.erb" => "This content.")
-
1
def stub_template(hash)
-
view.view_paths.unshift(ActionView::FixtureResolver.new(hash))
-
end
-
-
# Provides access to the params hash that will be available within the
-
# view.
-
#
-
# params[:foo] = 'bar'
-
1
def params
-
controller.params
-
end
-
-
# @deprecated Use `view` instead.
-
1
def template
-
RSpec.deprecate("template", :replacement => "view")
-
view
-
end
-
-
# @deprecated Use `rendered` instead.
-
1
def response
-
# `assert_template` expects `response` to implement a #body method
-
# like an `ActionDispatch::Response` does to force the view to
-
# render. For backwards compatibility, we use #response as an alias
-
# for #rendered, but it needs to implement #body to avoid
-
# `assert_template` raising a `NoMethodError`.
-
unless rendered.respond_to?(:body)
-
def rendered.body
-
self
-
end
-
end
-
-
rendered
-
end
-
-
1
private
-
-
1
def _default_render_options
-
if ::Rails::VERSION::STRING >= '3.2'
-
# pluck the handler, format, and locale out of, eg, posts/index.de.html.haml
-
template, *components = _default_file_to_render.split('.')
-
handler, format, locale = *components.reverse
-
-
render_options = { :template => template }
-
render_options[:handlers] = [handler] if handler
-
render_options[:formats] = [format.to_sym] if format
-
render_options[:locales] = [locale] if locale
-
-
render_options
-
else
-
{ :template => _default_file_to_render }
-
end
-
end
-
-
1
def _path_parts
-
_default_file_to_render.split("/")
-
end
-
-
1
def _controller_path
-
_path_parts[0..-2].join("/")
-
end
-
-
1
def _inferred_action
-
_path_parts.last.split(".").first
-
end
-
-
1
def _include_controller_helpers
-
helpers = controller._helpers
-
view.singleton_class.class_exec do
-
include helpers unless included_modules.include?(helpers)
-
end
-
end
-
end
-
-
1
included do
-
include ExampleMethods
-
-
helper(*_default_helpers)
-
-
before do
-
_include_controller_helpers
-
if view.lookup_context.respond_to?(:prefixes)
-
# rails 3.1
-
view.lookup_context.prefixes << _controller_path
-
end
-
-
controller.controller_path = _controller_path
-
controller.request.path_parameters[:controller] = _controller_path
-
controller.request.path_parameters[:action] = _inferred_action unless _inferred_action =~ /^_/
-
end
-
-
let(:_default_file_to_render) do |example|
-
example.example_group.top_level_description
-
end
-
end
-
end
-
end
-
end
-
1
require 'rspec/rails/extensions/active_record/proxy'
-
1
RSpec.configure do |rspec|
-
# Delay this in order to give users a chance to configure `expect_with`...
-
1
rspec.before(:suite) do
-
1
if defined?(RSpec::Matchers) && RSpec::Matchers.configuration.syntax.include?(:should) && defined?(ActiveRecord::Associations)
-
# In Rails 3.0, it was AssociationProxy.
-
# In 3.1+, it's CollectionProxy.
-
1
const_name = [:CollectionProxy, :AssociationProxy].find do |const|
-
1
ActiveRecord::Associations.const_defined?(const)
-
end
-
-
1
proxy_class = ActiveRecord::Associations.const_get(const_name)
-
-
1
RSpec::Matchers.configuration.add_should_and_should_not_to proxy_class
-
end
-
end
-
end
-
1
module RSpec
-
1
module Rails
-
# @private
-
1
module FixtureSupport
-
1
if defined?(ActiveRecord::TestFixtures)
-
1
extend ActiveSupport::Concern
-
1
include RSpec::Rails::SetupAndTeardownAdapter
-
1
include RSpec::Rails::MinitestLifecycleAdapter if ::ActiveRecord::VERSION::STRING > '4'
-
1
include RSpec::Rails::MinitestAssertionAdapter
-
1
include ActiveRecord::TestFixtures
-
-
1
included do
-
# TODO: (DC 2011-06-25) this is necessary because fixture_file_upload
-
# accesses fixture_path directly on ActiveSupport::TestCase. This is
-
# fixed in rails by https://github.com/rails/rails/pull/1861, which
-
# should be part of the 3.1 release, at which point we can include
-
# these lines for rails < 3.1.
-
6
ActiveSupport::TestCase.class_exec do
-
6
include ActiveRecord::TestFixtures
-
6
self.fixture_path = RSpec.configuration.fixture_path
-
end
-
# /TODO
-
-
6
self.fixture_path = RSpec.configuration.fixture_path
-
6
self.use_transactional_fixtures = RSpec.configuration.use_transactional_fixtures
-
6
self.use_instantiated_fixtures = RSpec.configuration.use_instantiated_fixtures
-
6
fixtures RSpec.configuration.global_fixtures if RSpec.configuration.global_fixtures
-
end
-
end
-
end
-
end
-
end
-
1
require 'rspec/core/warnings'
-
1
require 'rspec/expectations'
-
1
require 'rspec/rails/feature_check'
-
-
1
module RSpec
-
1
module Rails
-
# @api public
-
# Container module for Rails specific matchers.
-
1
module Matchers
-
end
-
end
-
end
-
-
1
require 'rspec/rails/matchers/have_rendered'
-
1
require 'rspec/rails/matchers/redirect_to'
-
1
require 'rspec/rails/matchers/routing_matchers'
-
1
require 'rspec/rails/matchers/be_new_record'
-
1
require 'rspec/rails/matchers/be_a_new'
-
1
require 'rspec/rails/matchers/relation_match_array'
-
1
require 'rspec/rails/matchers/be_valid'
-
1
require 'rspec/rails/matchers/have_http_status'
-
1
if RSpec::Rails::FeatureCheck.has_active_job?
-
1
require 'rspec/rails/matchers/active_job'
-
end
-
1
require "active_job/base"
-
1
require "active_job/arguments"
-
-
1
module RSpec
-
1
module Rails
-
1
module Matchers
-
# Namespace for various implementations of ActiveJob features
-
#
-
# @api private
-
1
module ActiveJob
-
# @private
-
1
class HaveEnqueuedJob < RSpec::Matchers::BuiltIn::BaseMatcher
-
1
def initialize(job)
-
@job = job
-
@args = []
-
@queue = nil
-
@at = nil
-
set_expected_number(:exactly, 1)
-
end
-
-
1
def matches?(proc)
-
raise ArgumentError, "have_enqueued_jobs only supports block expectations" unless Proc === proc
-
-
original_enqueued_jobs_count = queue_adapter.enqueued_jobs.count
-
proc.call
-
in_block_jobs = queue_adapter.enqueued_jobs.drop(original_enqueued_jobs_count)
-
-
@matching_jobs_count = in_block_jobs.count do |job|
-
serialized_attributes.all? { |key, value| value == job[key] }
-
end
-
-
case @expectation_type
-
when :exactly then @expected_number == @matching_jobs_count
-
when :at_most then @expected_number >= @matching_jobs_count
-
when :at_least then @expected_number <= @matching_jobs_count
-
end
-
end
-
-
1
def with(*args)
-
@args = args
-
self
-
end
-
-
1
def on_queue(queue)
-
@queue = queue
-
self
-
end
-
-
1
def at(date)
-
@at = date
-
self
-
end
-
-
1
def exactly(count)
-
set_expected_number(:exactly, count)
-
self
-
end
-
-
1
def at_least(count)
-
set_expected_number(:at_least, count)
-
self
-
end
-
-
1
def at_most(count)
-
set_expected_number(:at_most, count)
-
self
-
end
-
-
1
def times
-
self
-
end
-
-
1
def failure_message
-
"expected to enqueue #{base_message}"
-
end
-
-
1
def failure_message_when_negated
-
"expected not to enqueue #{base_message}"
-
end
-
-
1
def message_expectation_modifier
-
case @expectation_type
-
when :exactly then "exactly"
-
when :at_most then "at most"
-
when :at_least then "at least"
-
end
-
end
-
-
1
def supports_block_expectations?
-
true
-
end
-
-
1
private
-
-
1
def base_message
-
"#{message_expectation_modifier} #{@expected_number} jobs,".tap do |msg|
-
msg << " with #{@args}," if @args.any?
-
msg << " on queue #{@queue}," if @queue
-
msg << " at #{@at}," if @at
-
msg << " but enqueued #{@matching_jobs_count}"
-
end
-
end
-
-
1
def serialized_attributes
-
{}.tap do |attributes|
-
attributes[:args] = ::ActiveJob::Arguments.serialize(@args) if @args.any?
-
attributes[:at] = @at.to_f if @at
-
attributes[:queue] = @queue if @queue
-
attributes[:job] = @job if @job
-
end
-
end
-
-
1
def set_expected_number(relativity, count)
-
@expectation_type = relativity
-
@expected_number = case count
-
when :once then 1
-
when :twice then 2
-
when :thrice then 3
-
else Integer(count)
-
end
-
end
-
-
1
def queue_adapter
-
::ActiveJob::Base.queue_adapter
-
end
-
end
-
end
-
-
# @api public
-
# Passess if `count` of jobs were enqueued inside block
-
#
-
# @example
-
# expect {
-
# HeavyLiftingJob.perform_later
-
# }.to have_enqueued_job
-
#
-
# expect {
-
# HelloJob.perform_later
-
# HeavyLiftingJob.perform_later
-
# }.to have_enqueued_job(HelloJob).exactly(:once)
-
#
-
# expect {
-
# HelloJob.perform_later
-
# HelloJob.perform_later
-
# HelloJob.perform_later
-
# }.to have_enqueued_job(HelloJob).at_least(2).times
-
#
-
# expect {
-
# HelloJob.perform_later
-
# }.to have_enqueued_job(HelloJob).at_most(:twice)
-
#
-
# expect {
-
# HelloJob.perform_later
-
# HeavyLiftingJob.perform_later
-
# }.to have_enqueued_job(HelloJob).and have_enqueued_job(HeavyLiftingJob)
-
#
-
# expect {
-
# HelloJob.set(wait_until: Date.tomorrow.noon, queue: "low").perform_later(42)
-
# }.to have_enqueued_job.with(42).on_queue("low").at(Date.tomorrow.noon)
-
1
def have_enqueued_job(job = nil)
-
ActiveJob::HaveEnqueuedJob.new(job)
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Rails
-
1
module Matchers
-
# @api private
-
#
-
# Matcher class for `be_a_new`. Should not be instantiated directly.
-
#
-
# @see RSpec::Rails::Matchers#be_a_new
-
1
class BeANew < RSpec::Matchers::BuiltIn::BaseMatcher
-
# @private
-
1
def initialize(expected)
-
@expected = expected
-
end
-
-
# @private
-
1
def matches?(actual)
-
@actual = actual
-
actual.is_a?(expected) && actual.new_record? && attributes_match?(actual)
-
end
-
-
# @api public
-
# @see RSpec::Rails::Matchers#be_a_new
-
1
def with(expected_attributes)
-
attributes.merge!(expected_attributes)
-
self
-
end
-
-
# @private
-
1
def failure_message
-
[].tap do |message|
-
unless actual.is_a?(expected) && actual.new_record?
-
message << "expected #{actual.inspect} to be a new #{expected.inspect}"
-
end
-
unless attributes_match?(actual)
-
if unmatched_attributes.size > 1
-
message << "attributes #{unmatched_attributes.inspect} were not set on #{actual.inspect}"
-
else
-
message << "attribute #{unmatched_attributes.inspect} was not set on #{actual.inspect}"
-
end
-
end
-
end.join(' and ')
-
end
-
-
1
private
-
-
1
def attributes
-
@attributes ||= {}
-
end
-
-
1
def attributes_match?(actual)
-
attributes.stringify_keys.all? do |key, value|
-
actual.attributes[key].eql?(value)
-
end
-
end
-
-
1
def unmatched_attributes
-
attributes.stringify_keys.reject do |key, value|
-
actual.attributes[key].eql?(value)
-
end
-
end
-
end
-
-
# @api public
-
# Passes if actual is an instance of `model_class` and returns `false` for
-
# `persisted?`. Typically used to specify instance variables assigned to
-
# views by controller actions
-
#
-
# Use the `with` method to specify the specific attributes to match on the
-
# new record.
-
#
-
# @example
-
# get :new
-
# assigns(:thing).should be_a_new(Thing)
-
#
-
# post :create, :thing => { :name => "Illegal Value" }
-
# assigns(:thing).should be_a_new(Thing).with(:name => nil)
-
1
def be_a_new(model_class)
-
BeANew.new(model_class)
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Rails
-
1
module Matchers
-
# @private
-
1
class BeANewRecord < RSpec::Matchers::BuiltIn::BaseMatcher
-
1
def matches?(actual)
-
!actual.persisted?
-
end
-
-
1
def failure_message
-
"expected #{actual.inspect} to be a new record, but was persisted"
-
end
-
-
1
def failure_message_when_negated
-
"expected #{actual.inspect} to be persisted, but was a new record"
-
end
-
end
-
-
# @api public
-
# Passes if actual returns `false` for `persisted?`.
-
#
-
# @example
-
# get :new
-
# expect(assigns(:thing)).to be_new_record
-
1
def be_new_record
-
BeANewRecord.new
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Rails
-
1
module Matchers
-
# @private
-
1
class BeValid < RSpec::Matchers::BuiltIn::Be
-
1
def initialize(*args)
-
9
@args = args
-
end
-
-
1
def matches?(actual)
-
9
@actual = actual
-
9
actual.valid?(*@args)
-
end
-
-
1
def failure_message
-
message = "expected #{actual.inspect} to be valid"
-
-
if actual.respond_to?(:errors)
-
errors = if actual.errors.respond_to?(:full_messages)
-
actual.errors.full_messages
-
else
-
actual.errors
-
end
-
-
message << ", but got errors: #{errors.map(&:to_s).join(', ')}"
-
end
-
-
message
-
end
-
-
1
def failure_message_when_negated
-
"expected #{actual.inspect} not to be valid"
-
end
-
end
-
-
# @api public
-
# Passes if the given model instance's `valid?` method is true, meaning
-
# all of the `ActiveModel::Validations` passed and no errors exist. If a
-
# message is not given, a default message is shown listing each error.
-
#
-
# @example
-
# thing = Thing.new
-
# expect(thing).to be_valid
-
1
def be_valid(*args)
-
9
BeValid.new(*args)
-
end
-
end
-
end
-
end
-
# The following code inspired and modified from Rails' `assert_response`:
-
#
-
# https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/testing/assertions/response.rb#L22-L38
-
#
-
# Thank you to all the Rails devs who did the heavy lifting on this!
-
-
1
module RSpec
-
1
module Rails
-
1
module Matchers
-
# Namespace for various implementations of `have_http_status`.
-
#
-
# @api private
-
1
module HaveHttpStatus
-
# Instantiates an instance of the proper matcher based on the provided
-
# `target`.
-
#
-
# @param target [Object] expected http status or code
-
# @return response matcher instance
-
1
def self.matcher_for_status(target)
-
if GenericStatus.valid_statuses.include?(target)
-
GenericStatus.new(target)
-
elsif Symbol === target
-
SymbolicStatus.new(target)
-
else
-
NumericCode.new(target)
-
end
-
end
-
-
# @api private
-
# Conversion function to coerce the provided object into an
-
# `ActionDispatch::TestResponse`.
-
#
-
# @param obj [Object] object to convert to a response
-
# @return [ActionDispatch::TestResponse]
-
1
def as_test_response(obj)
-
if ::ActionDispatch::Response === obj
-
::ActionDispatch::TestResponse.from_response(obj)
-
elsif ::ActionDispatch::TestResponse === obj
-
obj
-
elsif obj.respond_to?(:status_code) && obj.respond_to?(:response_headers)
-
# Acts As Capybara Session
-
# Hack to support `Capybara::Session` without having to load
-
# Capybara or catch `NameError`s for the undefined constants
-
::ActionDispatch::TestResponse.new.tap do |resp|
-
resp.status = obj.status_code
-
resp.headers = obj.response_headers
-
resp.body = obj.body
-
end
-
else
-
raise TypeError, "Invalid response type: #{obj}"
-
end
-
end
-
1
module_function :as_test_response
-
-
# @return [String, nil] a formatted failure message if
-
# `@invalid_response` is present, `nil` otherwise
-
1
def invalid_response_type_message
-
return unless @invalid_response
-
"expected a response object, but an instance of " \
-
"#{@invalid_response.class} was received"
-
end
-
-
# @api private
-
# Provides an implementation for `have_http_status` matching against
-
# numeric http status codes.
-
#
-
# Not intended to be instantiated directly.
-
#
-
# @example
-
# expect(response).to have_http_status(404)
-
#
-
# @see RSpec::Rails::Matchers.have_http_status
-
1
class NumericCode < RSpec::Matchers::BuiltIn::BaseMatcher
-
1
include HaveHttpStatus
-
-
1
def initialize(code)
-
@expected = code.to_i
-
@actual = nil
-
@invalid_response = nil
-
end
-
-
# @param [Object] response object providing an http code to match
-
# @return [Boolean] `true` if the numeric code matched the `response` code
-
1
def matches?(response)
-
test_response = as_test_response(response)
-
@actual = test_response.response_code
-
expected == @actual
-
rescue TypeError => _ignored
-
@invalid_response = response
-
false
-
end
-
-
# @return [String]
-
1
def description
-
"respond with numeric status code #{expected}"
-
end
-
-
# @return [String] explaining why the match failed
-
1
def failure_message
-
invalid_response_type_message ||
-
"expected the response to have status code #{expected.inspect}" \
-
" but it was #{actual.inspect}"
-
end
-
-
# @return [String] explaining why the match failed
-
1
def failure_message_when_negated
-
invalid_response_type_message ||
-
"expected the response not to have status code " \
-
"#{expected.inspect} but it did"
-
end
-
end
-
-
# @api private
-
# Provides an implementation for `have_http_status` matching against
-
# Rack symbol http status codes.
-
#
-
# Not intended to be instantiated directly.
-
#
-
# @example
-
# expect(response).to have_http_status(:created)
-
#
-
# @see RSpec::Rails::Matchers.have_http_status
-
# @see https://github.com/rack/rack/blob/master/lib/rack/utils.rb `Rack::Utils::SYMBOL_TO_STATUS_CODE`
-
1
class SymbolicStatus < RSpec::Matchers::BuiltIn::BaseMatcher
-
1
include HaveHttpStatus
-
-
1
def initialize(status)
-
@expected_status = status
-
@actual = nil
-
@invalid_response = nil
-
set_expected_code!
-
end
-
-
# @param [Object] response object providing an http code to match
-
# @return [Boolean] `true` if Rack's associated numeric HTTP code matched
-
# the `response` code
-
1
def matches?(response)
-
test_response = as_test_response(response)
-
@actual = test_response.response_code
-
expected == @actual
-
rescue TypeError => _ignored
-
@invalid_response = response
-
false
-
end
-
-
# @return [String]
-
1
def description
-
"respond with status code #{pp_expected}"
-
end
-
-
# @return [String] explaining why the match failed
-
1
def failure_message
-
invalid_response_type_message ||
-
"expected the response to have status code #{pp_expected} but it" \
-
" was #{pp_actual}"
-
end
-
-
# @return [String] explaining why the match failed
-
1
def failure_message_when_negated
-
invalid_response_type_message ||
-
"expected the response not to have status code #{pp_expected} " \
-
"but it did"
-
end
-
-
# The initialized expected status symbol
-
1
attr_reader :expected_status
-
1
private :expected_status
-
-
1
private
-
-
# @return [Symbol] representing the actual http numeric code
-
1
def actual_status
-
return unless actual
-
@actual_status ||= compute_status_from(actual)
-
end
-
-
# Reverse lookup of the Rack status code symbol based on the numeric
-
# http code
-
#
-
# @param code [Fixnum] http status code to look up
-
# @return [Symbol] representing the http numeric code
-
1
def compute_status_from(code)
-
status, _ = Rack::Utils::SYMBOL_TO_STATUS_CODE.find do |_, c|
-
c == code
-
end
-
status
-
end
-
-
# @return [String] pretty format the actual response status
-
1
def pp_actual
-
pp_status(actual_status, actual)
-
end
-
-
# @return [String] pretty format the expected status and associated code
-
1
def pp_expected
-
pp_status(expected_status, expected)
-
end
-
-
# @return [String] pretty format the actual response status
-
1
def pp_status(status, code)
-
if status
-
"#{status.inspect} (#{code})"
-
else
-
code.to_s
-
end
-
end
-
-
# Sets `expected` to the numeric http code based on the Rack
-
# `expected_status` status
-
#
-
# @see Rack::Utils::SYMBOL_TO_STATUS_CODE
-
# @raise [ArgumentError] if an associated code could not be found
-
1
def set_expected_code!
-
@expected ||=
-
Rack::Utils::SYMBOL_TO_STATUS_CODE.fetch(expected_status) do
-
raise ArgumentError,
-
"Invalid HTTP status: #{expected_status.inspect}"
-
end
-
end
-
end
-
-
# @api private
-
# Provides an implementation for `have_http_status` matching against
-
# `ActionDispatch::TestResponse` http status category queries.
-
#
-
# Not intended to be instantiated directly.
-
#
-
# @example
-
# expect(response).to have_http_status(:success)
-
# expect(response).to have_http_status(:error)
-
# expect(response).to have_http_status(:missing)
-
# expect(response).to have_http_status(:redirect)
-
#
-
# @see RSpec::Rails::Matchers.have_http_status
-
# @see ActionDispatch::TestResponse
-
1
class GenericStatus < RSpec::Matchers::BuiltIn::BaseMatcher
-
1
include HaveHttpStatus
-
-
# @return [Array<Symbol>] of status codes which represent a HTTP status
-
# code "group"
-
# @see https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/testing/test_response.rb `ActionDispatch::TestResponse`
-
1
def self.valid_statuses
-
[:error, :success, :missing, :redirect]
-
end
-
-
1
def initialize(type)
-
unless self.class.valid_statuses.include?(type)
-
raise ArgumentError, "Invalid generic HTTP status: #{type.inspect}"
-
end
-
@expected = type
-
@actual = nil
-
@invalid_response = nil
-
end
-
-
# @return [Boolean] `true` if Rack's associated numeric HTTP code matched
-
# the `response` code
-
1
def matches?(response)
-
test_response = as_test_response(response)
-
@actual = test_response.response_code
-
test_response.send("#{expected}?")
-
rescue TypeError => _ignored
-
@invalid_response = response
-
false
-
end
-
-
# @return [String]
-
1
def description
-
"respond with #{type_message}"
-
end
-
-
# @return [String] explaining why the match failed
-
1
def failure_message
-
invalid_response_type_message ||
-
"expected the response to have #{type_message} but it was #{actual}"
-
end
-
-
# @return [String] explaining why the match failed
-
1
def failure_message_when_negated
-
invalid_response_type_message ||
-
"expected the response not to have #{type_message} but it was #{actual}"
-
end
-
-
1
private
-
-
# @return [String] formating the expected status and associated code(s)
-
1
def type_message
-
@type_message ||= (expected == :error ? "an error" : "a #{expected}") +
-
" status code (#{type_codes})"
-
end
-
-
# @return [String] formatting the associated code(s) for the various
-
# status code "groups"
-
# @see https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/testing/test_response.rb `ActionDispatch::TestResponse`
-
# @see https://github.com/rack/rack/blob/master/lib/rack/response.rb `Rack::Response`
-
1
def type_codes
-
# At the time of this commit the most recent version of
-
# `ActionDispatch::TestResponse` defines the following aliases:
-
#
-
# alias_method :success?, :successful?
-
# alias_method :missing?, :not_found?
-
# alias_method :redirect?, :redirection?
-
# alias_method :error?, :server_error?
-
#
-
# It's parent `ActionDispatch::Response` includes
-
# `Rack::Response::Helpers` which defines the aliased methods as:
-
#
-
# def successful?; status >= 200 && status < 300; end
-
# def redirection?; status >= 300 && status < 400; end
-
# def server_error?; status >= 500 && status < 600; end
-
# def not_found?; status == 404; end
-
#
-
# @see https://github.com/rails/rails/blob/ca200378/actionpack/lib/action_dispatch/testing/test_response.rb#L17-L27
-
# @see https://github.com/rails/rails/blob/ca200378/actionpack/lib/action_dispatch/http/response.rb#L74
-
# @see https://github.com/rack/rack/blob/ce4a3959/lib/rack/response.rb#L119-L122
-
@type_codes ||= case expected
-
when :error
-
"5xx"
-
when :success
-
"2xx"
-
when :missing
-
"404"
-
when :redirect
-
"3xx"
-
end
-
end
-
end
-
end
-
-
# @api public
-
# Passes if `response` has a matching HTTP status code.
-
#
-
# The following symbolic status codes are allowed:
-
#
-
# - `Rack::Utils::SYMBOL_TO_STATUS_CODE`
-
# - One of the defined `ActionDispatch::TestResponse` aliases:
-
# - `:error`
-
# - `:missing`
-
# - `:redirect`
-
# - `:success`
-
#
-
# @example Accepts numeric and symbol statuses
-
# expect(response).to have_http_status(404)
-
# expect(response).to have_http_status(:created)
-
# expect(response).to have_http_status(:success)
-
# expect(response).to have_http_status(:error)
-
# expect(response).to have_http_status(:missing)
-
# expect(response).to have_http_status(:redirect)
-
#
-
# @example Works with standard `response` objects and Capybara's `page`
-
# expect(response).to have_http_status(404)
-
# expect(page).to have_http_status(:created)
-
#
-
# @see https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/testing/test_response.rb `ActionDispatch::TestResponse`
-
# @see https://github.com/rack/rack/blob/master/lib/rack/utils.rb `Rack::Utils::SYMBOL_TO_STATUS_CODE`
-
1
def have_http_status(target)
-
raise ArgumentError, "Invalid HTTP status: nil" unless target
-
HaveHttpStatus.matcher_for_status(target)
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Rails
-
1
module Matchers
-
# Matcher for template rendering.
-
1
module RenderTemplate
-
# @private
-
1
class RenderTemplateMatcher < RSpec::Matchers::BuiltIn::BaseMatcher
-
1
def initialize(scope, expected, message = nil)
-
@expected = Symbol === expected ? expected.to_s : expected
-
@message = message
-
@scope = scope
-
@redirect_is = nil
-
end
-
-
# @api private
-
1
def matches?(*)
-
match_check = match_unless_raises ActiveSupport::TestCase::Assertion do
-
@scope.assert_template expected, @message
-
end
-
check_redirect unless match_check
-
match_check
-
end
-
-
# Uses normalize_argument_to_redirection to find and format
-
# the redirect location. normalize_argument_to_redirection is private
-
# in ActionDispatch::Assertions::ResponseAssertions so we call it
-
# here using #send. This will keep the error message format consistent
-
# @api private
-
1
def check_redirect
-
response = @scope.response
-
return unless response.respond_to?(:redirect?) && response.redirect?
-
@redirect_is = @scope.send(:normalize_argument_to_redirection, response.location)
-
end
-
-
# @api private
-
1
def failure_message
-
if @redirect_is
-
rescued_exception.message[/.* but /] +
-
"was a redirect to <#{@redirect_is}>"
-
else
-
rescued_exception.message
-
end
-
end
-
-
# @api private
-
1
def failure_message_when_negated
-
"expected not to render #{expected.inspect}, but did"
-
end
-
end
-
-
# Delegates to `assert_template`.
-
#
-
# @example
-
# expect(response).to have_rendered("new")
-
1
def have_rendered(options, message = nil)
-
RenderTemplateMatcher.new(self, options, message)
-
end
-
-
1
alias_method :render_template, :have_rendered
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Rails
-
1
module Matchers
-
# Matcher for redirects.
-
1
module RedirectTo
-
# @private
-
1
class RedirectTo < RSpec::Matchers::BuiltIn::BaseMatcher
-
1
def initialize(scope, expected)
-
@expected = expected
-
@scope = scope
-
end
-
-
1
def matches?(_)
-
match_unless_raises ActiveSupport::TestCase::Assertion do
-
@scope.assert_redirected_to(@expected)
-
end
-
end
-
-
1
def failure_message
-
rescued_exception.message
-
end
-
-
1
def failure_message_when_negated
-
"expected not to redirect to #{@expected.inspect}, but did"
-
end
-
end
-
-
# Delegates to `assert_redirected_to`.
-
#
-
# @example
-
# expect(response).to redirect_to(:action => "new")
-
1
def redirect_to(target)
-
RedirectTo.new(self, target)
-
end
-
end
-
end
-
end
-
end
-
1
if defined?(ActiveRecord::Relation)
-
1
RSpec::Matchers::BuiltIn::OperatorMatcher.register(ActiveRecord::Relation, '=~', RSpec::Matchers::BuiltIn::ContainExactly)
-
end
-
1
module RSpec
-
1
module Rails
-
1
module Matchers
-
# Matchers to help with specs for routing code.
-
1
module RoutingMatchers
-
1
extend RSpec::Matchers::DSL
-
-
# @private
-
1
class RouteToMatcher < RSpec::Matchers::BuiltIn::BaseMatcher
-
1
def initialize(scope, *expected)
-
@scope = scope
-
@expected = expected[1] || {}
-
if Hash === expected[0]
-
@expected.merge!(expected[0])
-
else
-
controller, action = expected[0].split('#')
-
@expected.merge!(:controller => controller, :action => action)
-
end
-
end
-
-
1
def matches?(verb_to_path_map)
-
@actual = @verb_to_path_map = verb_to_path_map
-
# assert_recognizes does not consider ActionController::RoutingError an
-
# assertion failure, so we have to capture that and Assertion here.
-
match_unless_raises ActiveSupport::TestCase::Assertion, ActionController::RoutingError do
-
path, query = *verb_to_path_map.values.first.split('?')
-
@scope.assert_recognizes(
-
@expected,
-
{ :method => verb_to_path_map.keys.first, :path => path },
-
Rack::Utils.parse_nested_query(query)
-
)
-
end
-
end
-
-
1
def failure_message
-
rescued_exception.message
-
end
-
-
1
def failure_message_when_negated
-
"expected #{@actual.inspect} not to route to #{@expected.inspect}"
-
end
-
-
1
def description
-
"route #{@actual.inspect} to #{@expected.inspect}"
-
end
-
end
-
-
# Delegates to `assert_recognizes`. Supports short-hand controller/action
-
# declarations (e.g. `"controller#action"`).
-
#
-
# @example
-
#
-
# expect(:get => "/things/special").to route_to(
-
# :controller => "things",
-
# :action => "special"
-
# )
-
#
-
# expect(:get => "/things/special").to route_to("things#special")
-
#
-
# @see http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html#method-i-assert_recognizes
-
1
def route_to(*expected)
-
RouteToMatcher.new(self, *expected)
-
end
-
-
# @private
-
1
class BeRoutableMatcher < RSpec::Matchers::BuiltIn::BaseMatcher
-
1
def initialize(scope)
-
@scope = scope
-
end
-
-
1
def matches?(path)
-
@actual = path
-
match_unless_raises ActionController::RoutingError do
-
@routing_options = @scope.routes.recognize_path(
-
path.values.first, :method => path.keys.first
-
)
-
end
-
end
-
-
1
def failure_message
-
"expected #{@actual.inspect} to be routable"
-
end
-
-
1
def failure_message_when_negated
-
"expected #{@actual.inspect} not to be routable, but it routes to #{@routing_options.inspect}"
-
end
-
-
1
def description
-
"be routable"
-
end
-
end
-
-
# Passes if the route expression is recognized by the Rails router based on
-
# the declarations in `config/routes.rb`. Delegates to
-
# `RouteSet#recognize_path`.
-
#
-
# @example You can use route helpers provided by rspec-rails.
-
# expect(:get => "/a/path").to be_routable
-
# expect(:post => "/another/path").to be_routable
-
# expect(:put => "/yet/another/path").to be_routable
-
1
def be_routable
-
BeRoutableMatcher.new(self)
-
end
-
-
# Helpers for matching different route types.
-
1
module RouteHelpers
-
# @!method get
-
# @!method post
-
# @!method put
-
# @!method patch
-
# @!method delete
-
# @!method options
-
# @!method head
-
#
-
# Shorthand method for matching this type of route.
-
1
%w[get post put patch delete options head].each do |method|
-
7
define_method method do |path|
-
{ method.to_sym => path }
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
1
begin
-
1
require 'capybara/rspec'
-
rescue LoadError
-
end
-
-
1
begin
-
1
require 'capybara/rails'
-
rescue LoadError
-
end
-
-
1
if defined?(Capybara)
-
1
require 'rspec/support/version_checker'
-
1
RSpec::Support::VersionChecker.new('capybara', Capybara::VERSION, '2.2.0').check_version!
-
-
1
RSpec.configure do |c|
-
1
if defined?(Capybara::DSL)
-
1
c.include Capybara::DSL, :type => :feature
-
end
-
-
1
if defined?(Capybara::RSpecMatchers)
-
1
c.include Capybara::RSpecMatchers, :type => :view
-
1
c.include Capybara::RSpecMatchers, :type => :helper
-
1
c.include Capybara::RSpecMatchers, :type => :mailer
-
1
c.include Capybara::RSpecMatchers, :type => :controller
-
1
c.include Capybara::RSpecMatchers, :type => :feature
-
end
-
-
1
unless defined?(Capybara::RSpecMatchers) || defined?(Capybara::DSL)
-
c.include Capybara, :type => :request
-
c.include Capybara, :type => :controller
-
end
-
end
-
end
-
1
module RSpec
-
1
module Rails
-
# Helpers for making instance variables available to views.
-
1
module ViewAssigns
-
# Assigns a value to an instance variable in the scope of the
-
# view being rendered.
-
#
-
# @example
-
#
-
# assign(:widget, stub_model(Widget))
-
1
def assign(key, value)
-
_encapsulated_assigns[key] = value
-
end
-
-
# Compat-shim for AbstractController::Rendering#view_assigns
-
#
-
# _assigns was deprecated in favor of view_assigns after
-
# Rails-3.0.0 was released. Since we are not able to predict when
-
# the _assigns/view_assigns patch will be released (I thought it
-
# would have been in 3.0.1, but 3.0.1 bypassed this change for a
-
# security fix), this bit ensures that we do the right thing without
-
# knowing anything about the Rails version we are dealing with.
-
#
-
# Once that change _is_ released, this can be changed to something
-
# that checks for the Rails version when the module is being
-
# interpreted, as it was before commit dd0095.
-
1
def view_assigns
-
super.merge(_encapsulated_assigns)
-
rescue
-
_assigns
-
end
-
-
# @private
-
1
def _assigns
-
super.merge(_encapsulated_assigns)
-
end
-
-
1
private
-
-
1
def _encapsulated_assigns
-
@_encapsulated_assigns ||= {}
-
end
-
end
-
end
-
end
-
1
require 'action_view/testing/resolvers'
-
-
1
module RSpec
-
1
module Rails
-
# @api public
-
# Helpers for optionally rendering views in controller specs.
-
1
module ViewRendering
-
1
extend ActiveSupport::Concern
-
-
# @!attribute [r]
-
# Returns the controller object instance under test.
-
1
attr_reader :controller
-
-
# @private
-
1
attr_writer :controller
-
1
private :controller=
-
-
# DSL methods
-
1
module ClassMethods
-
# @see RSpec::Rails::ControllerExampleGroup
-
1
def render_views(true_or_false = true)
-
@render_views = true_or_false
-
end
-
-
# @api private
-
1
def render_views?
-
82
return @render_views if defined?(@render_views)
-
-
82
if superclass.respond_to?(:render_views?)
-
46
superclass.render_views?
-
else
-
36
RSpec.configuration.render_views?
-
end
-
end
-
end
-
-
# @api private
-
1
def render_views?
-
36
self.class.render_views? || !controller.class.respond_to?(:view_paths)
-
end
-
-
# Delegates find_all to the submitted path set and then returns templates
-
# with modified source
-
#
-
# @private
-
1
class EmptyTemplateResolver < ::ActionView::FileSystemResolver
-
1
private
-
-
1
def find_templates(*args)
-
9
super.map do |template|
-
6
::ActionView::Template.new(
-
"",
-
template.identifier,
-
EmptyTemplateHandler,
-
:virtual_path => template.virtual_path,
-
:format => template.formats
-
)
-
end
-
end
-
end
-
-
# @private
-
1
class EmptyTemplateHandler
-
1
def self.call(_template)
-
6
%("")
-
end
-
end
-
-
# Used to null out view rendering in controller specs.
-
#
-
# @private
-
1
module EmptyTemplates
-
1
def prepend_view_path(new_path)
-
lookup_context.view_paths.unshift(*_path_decorator(new_path))
-
end
-
-
1
def append_view_path(new_path)
-
lookup_context.view_paths.push(*_path_decorator(new_path))
-
end
-
-
1
private
-
-
1
def _path_decorator(path)
-
EmptyTemplateResolver.new(path)
-
end
-
end
-
-
# @private
-
1
RESOLVER_CACHE = Hash.new do |hash, path|
-
1
hash[path] = EmptyTemplateResolver.new(path)
-
end
-
-
1
included do
-
4
before do
-
18
unless render_views?
-
18
@_original_path_set = controller.class.view_paths
-
36
path_set = @_original_path_set.map { |resolver| RESOLVER_CACHE[resolver.to_s] }
-
-
18
controller.class.view_paths = path_set
-
18
controller.extend(EmptyTemplates)
-
end
-
end
-
-
4
after do
-
18
controller.class.view_paths = @_original_path_set unless render_views?
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Support
-
# Provides a means to fuzzy-match between two arbitrary objects.
-
# Understands array/hash nesting. Uses `===` or `==` to
-
# perform the matching.
-
1
module FuzzyMatcher
-
# @api private
-
1
def self.values_match?(expected, actual)
-
4
if Hash === actual
-
return hashes_match?(expected, actual) if Hash === expected
-
elsif Array === expected && Enumerable === actual && !(Struct === actual)
-
return arrays_match?(expected, actual.to_a)
-
end
-
-
4
return true if expected == actual
-
-
4
begin
-
4
expected === actual
-
rescue ArgumentError
-
# Some objects, like 0-arg lambdas on 1.9+, raise
-
# ArgumentError for `expected === actual`.
-
false
-
end
-
end
-
-
# @private
-
1
def self.arrays_match?(expected_list, actual_list)
-
return false if expected_list.size != actual_list.size
-
-
expected_list.zip(actual_list).all? do |expected, actual|
-
values_match?(expected, actual)
-
end
-
end
-
-
# @private
-
1
def self.hashes_match?(expected_hash, actual_hash)
-
return false if expected_hash.size != actual_hash.size
-
-
expected_hash.all? do |expected_key, expected_value|
-
actual_value = actual_hash.fetch(expected_key) { return false }
-
values_match?(expected_value, actual_value)
-
end
-
end
-
-
1
private_class_method :arrays_match?, :hashes_match?
-
end
-
end
-
end
-
1
module RSpec
-
1
module Support
-
# @private
-
1
def self.matcher_definitions
-
2
@matcher_definitions ||= []
-
end
-
-
# Used internally to break cyclic dependency between mocks, expectations,
-
# and support. We don't currently have a consistent implementation of our
-
# matchers, though we are considering changing that:
-
# https://github.com/rspec/rspec-mocks/issues/513
-
#
-
# @private
-
1
def self.register_matcher_definition(&block)
-
2
matcher_definitions << block
-
end
-
-
# Remove a previously registered matcher. Useful for cleaning up after
-
# yourself in specs.
-
#
-
# @private
-
1
def self.deregister_matcher_definition(&block)
-
matcher_definitions.delete(block)
-
end
-
-
# @private
-
1
def self.is_a_matcher?(object)
-
matcher_definitions.any? { |md| md.call(object) }
-
end
-
-
# @api private
-
#
-
# gives a string representation of an object for use in RSpec descriptions
-
1
def self.rspec_description_for_object(object)
-
if RSpec::Support.is_a_matcher?(object) && object.respond_to?(:description)
-
object.description
-
else
-
object
-
end
-
end
-
end
-
end
-
1
require 'rspec/support'
-
1
RSpec::Support.require_rspec_support "ruby_features"
-
1
RSpec::Support.require_rspec_support "matcher_definition"
-
-
1
module RSpec
-
1
module Support
-
# Extracts info about the number of arguments and allowed/required
-
# keyword args of a given method.
-
#
-
# @private
-
1
class MethodSignature
-
1
attr_reader :min_non_kw_args, :max_non_kw_args, :optional_kw_args, :required_kw_args
-
-
1
def initialize(method)
-
@method = method
-
@optional_kw_args = []
-
@required_kw_args = []
-
classify_parameters
-
end
-
-
1
def non_kw_args_arity_description
-
case max_non_kw_args
-
when min_non_kw_args then min_non_kw_args.to_s
-
when INFINITY then "#{min_non_kw_args} or more"
-
else "#{min_non_kw_args} to #{max_non_kw_args}"
-
end
-
end
-
-
1
def valid_non_kw_args?(positional_arg_count)
-
min_non_kw_args <= positional_arg_count &&
-
positional_arg_count <= max_non_kw_args
-
end
-
-
1
if RubyFeatures.optional_and_splat_args_supported?
-
1
def description
-
@description ||= begin
-
parts = []
-
-
unless non_kw_args_arity_description == "0"
-
parts << "arity of #{non_kw_args_arity_description}"
-
end
-
-
if @optional_kw_args.any?
-
parts << "optional keyword args (#{@optional_kw_args.map(&:inspect).join(", ")})"
-
end
-
-
if @required_kw_args.any?
-
parts << "required keyword args (#{@required_kw_args.map(&:inspect).join(", ")})"
-
end
-
-
parts << "any additional keyword args" if @allows_any_kw_args
-
-
parts.join(" and ")
-
end
-
end
-
-
1
def missing_kw_args_from(given_kw_args)
-
@required_kw_args - given_kw_args
-
end
-
-
1
def invalid_kw_args_from(given_kw_args)
-
return [] if @allows_any_kw_args
-
given_kw_args - @allowed_kw_args
-
end
-
-
1
def has_kw_args_in?(args)
-
Hash === args.last && could_contain_kw_args?(args)
-
end
-
-
# Without considering what the last arg is, could it
-
# contain keyword arguments?
-
1
def could_contain_kw_args?(args)
-
return false if args.count <= min_non_kw_args
-
@allows_any_kw_args || @allowed_kw_args.any?
-
end
-
-
1
def classify_parameters
-
optional_non_kw_args = @min_non_kw_args = 0
-
@allows_any_kw_args = false
-
-
@method.parameters.each do |(type, name)|
-
case type
-
# def foo(a:)
-
when :keyreq then @required_kw_args << name
-
# def foo(a: 1)
-
when :key then @optional_kw_args << name
-
# def foo(**kw_args)
-
when :keyrest then @allows_any_kw_args = true
-
# def foo(a)
-
when :req then @min_non_kw_args += 1
-
# def foo(a = 1)
-
when :opt then optional_non_kw_args += 1
-
# def foo(*a)
-
when :rest then optional_non_kw_args = INFINITY
-
end
-
end
-
-
@max_non_kw_args = @min_non_kw_args + optional_non_kw_args
-
@allowed_kw_args = @required_kw_args + @optional_kw_args
-
end
-
else
-
def description
-
"arity of #{non_kw_args_arity_description}"
-
end
-
-
def missing_kw_args_from(_given_kw_args)
-
[]
-
end
-
-
def invalid_kw_args_from(_given_kw_args)
-
[]
-
end
-
-
def has_kw_args_in?(_args)
-
false
-
end
-
-
def could_contain_kw_args?(*)
-
false
-
end
-
-
def classify_parameters
-
arity = @method.arity
-
if arity < 0
-
# `~` inverts the one's complement and gives us the
-
# number of required args
-
@min_non_kw_args = ~arity
-
@max_non_kw_args = INFINITY
-
else
-
@min_non_kw_args = arity
-
@max_non_kw_args = arity
-
end
-
end
-
end
-
-
1
INFINITY = 1 / 0.0
-
end
-
-
# Some versions of JRuby have a nasty bug we have to work around :(.
-
# https://github.com/jruby/jruby/issues/2816
-
if RSpec::Support::Ruby.jruby? &&
-
1
RubyFeatures.optional_and_splat_args_supported? &&
-
Class.new { attr_writer :foo }.instance_method(:foo=).parameters == []
-
-
class MethodSignature < remove_const(:MethodSignature)
-
private
-
-
def classify_parameters
-
super
-
return unless @method.parameters == [] && @method.arity == 1
-
@max_non_kw_args = @min_non_kw_args = 1
-
end
-
end
-
end
-
-
# Deals with the slightly different semantics of block arguments.
-
# For methods, arguments are required unless a default value is provided.
-
# For blocks, arguments are optional, even if no default value is provided.
-
#
-
# However, we want to treat block args as required since you virtually
-
# always want to pass a value for each received argument and our
-
# `and_yield` has treated block args as required for many years.
-
#
-
# @api private
-
1
class BlockSignature < MethodSignature
-
1
if RubyFeatures.optional_and_splat_args_supported?
-
1
def classify_parameters
-
super
-
@min_non_kw_args = @max_non_kw_args unless @max_non_kw_args == INFINITY
-
end
-
end
-
end
-
-
# Abstract base class for signature verifiers.
-
#
-
# @api private
-
1
class MethodSignatureVerifier
-
1
attr_reader :non_kw_args, :kw_args
-
-
1
def initialize(signature, args)
-
@signature = signature
-
@non_kw_args, @kw_args = split_args(*args)
-
end
-
-
1
def valid?
-
missing_kw_args.empty? &&
-
invalid_kw_args.empty? &&
-
valid_non_kw_args?
-
end
-
-
1
def error_message
-
if missing_kw_args.any?
-
"Missing required keyword arguments: %s" % [
-
missing_kw_args.join(", ")
-
]
-
elsif invalid_kw_args.any?
-
"Invalid keyword arguments provided: %s" % [
-
invalid_kw_args.join(", ")
-
]
-
elsif !valid_non_kw_args?
-
"Wrong number of arguments. Expected %s, got %s." % [
-
@signature.non_kw_args_arity_description,
-
non_kw_args.length
-
]
-
end
-
end
-
-
1
private
-
-
1
def valid_non_kw_args?
-
@signature.valid_non_kw_args?(non_kw_args.length)
-
end
-
-
1
def missing_kw_args
-
@signature.missing_kw_args_from(kw_args)
-
end
-
-
1
def invalid_kw_args
-
@signature.invalid_kw_args_from(kw_args)
-
end
-
-
1
def split_args(*args)
-
kw_args = if @signature.has_kw_args_in?(args)
-
args.pop.keys
-
else
-
[]
-
end
-
-
[args, kw_args]
-
end
-
end
-
-
# Figures out wether a given method can accept various arguments.
-
# Surprisingly non-trivial.
-
#
-
# @private
-
1
StrictSignatureVerifier = MethodSignatureVerifier
-
-
# Allows matchers to be used instead of providing keyword arguments. In
-
# practice, when this happens only the arity of the method is verified.
-
#
-
# @private
-
1
class LooseSignatureVerifier < MethodSignatureVerifier
-
1
private
-
-
1
def split_args(*args)
-
if RSpec::Support.is_a_matcher?(args.last) && @signature.could_contain_kw_args?(args)
-
args.pop
-
@signature = SignatureWithKeywordArgumentsMatcher.new(@signature)
-
end
-
-
super(*args)
-
end
-
-
# If a matcher is used in a signature in place of keyword arguments, all
-
# keyword argument validation needs to be skipped since the matcher is
-
# opaque.
-
#
-
# Instead, keyword arguments will be validated when the method is called
-
# and they are actually known.
-
#
-
# @private
-
1
class SignatureWithKeywordArgumentsMatcher
-
1
def initialize(signature)
-
@signature = signature
-
end
-
-
1
def missing_kw_args_from(_kw_args)
-
[]
-
end
-
-
1
def invalid_kw_args_from(_kw_args)
-
[]
-
end
-
-
1
def non_kw_args_arity_description
-
@signature.non_kw_args_arity_description
-
end
-
-
1
def valid_non_kw_args?(*args)
-
@signature.valid_non_kw_args?(*args)
-
end
-
-
1
def has_kw_args_in?(args)
-
@signature.has_kw_args_in?(args)
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Support
-
# Provide additional output details beyond what `inspect` provides when
-
# printing Time, DateTime, or BigDecimal
-
1
module ObjectFormatter
-
# @api private
-
1
def self.format(object)
-
prepare_for_inspection(object).inspect
-
end
-
-
# rubocop:disable MethodLength
-
-
# @private
-
# Prepares the provided object to be formatted by wrapping it as needed
-
# in something that, when `inspect` is called on it, will produce the
-
# desired output.
-
#
-
# This allows us to apply the desired formatting to hash/array data structures
-
# at any level of nesting, simply by walking that structure and replacing items
-
# with custom items that have `inspect` defined to return the desired output
-
# for that item. Then we can just use `Array#inspect` or `Hash#inspect` to
-
# format the entire thing.
-
1
def self.prepare_for_inspection(object)
-
case object
-
when Array
-
return object.map { |o| prepare_for_inspection(o) }
-
when Hash
-
return prepare_hash(object)
-
when Time
-
inspection = format_time(object)
-
else
-
if defined?(DateTime) && DateTime === object
-
inspection = format_date_time(object)
-
elsif defined?(BigDecimal) && BigDecimal === object
-
inspection = "#{object.to_s 'F'} (#{object.inspect})"
-
elsif RSpec::Support.is_a_matcher?(object) && object.respond_to?(:description)
-
inspection = object.description
-
else
-
return DelegatingInspector.new(object)
-
end
-
end
-
-
InspectableItem.new(inspection)
-
end
-
# rubocop:enable MethodLength
-
-
# @private
-
1
def self.prepare_hash(input)
-
input.inject({}) do |hash, (k, v)|
-
hash[prepare_for_inspection(k)] = prepare_for_inspection(v)
-
hash
-
end
-
end
-
-
1
TIME_FORMAT = "%Y-%m-%d %H:%M:%S"
-
-
1
if Time.method_defined?(:nsec)
-
# @private
-
1
def self.format_time(time)
-
time.strftime("#{TIME_FORMAT}.#{"%09d" % time.nsec} %z")
-
end
-
else # for 1.8.7
-
# @private
-
def self.format_time(time)
-
time.strftime("#{TIME_FORMAT}.#{"%06d" % time.usec} %z")
-
end
-
end
-
-
1
DATE_TIME_FORMAT = "%a, %d %b %Y %H:%M:%S.%N %z"
-
# ActiveSupport sometimes overrides inspect. If `ActiveSupport` is
-
# defined use a custom format string that includes more time precision.
-
# @private
-
1
def self.format_date_time(date_time)
-
if defined?(ActiveSupport)
-
date_time.strftime(DATE_TIME_FORMAT)
-
else
-
date_time.inspect
-
end
-
end
-
-
# @private
-
1
InspectableItem = Struct.new(:inspection) do
-
1
def inspect
-
inspection
-
end
-
-
1
def pretty_print(pp)
-
pp.text inspection
-
end
-
end
-
-
# @private
-
1
DelegatingInspector = Struct.new(:object) do
-
1
def inspect
-
if defined?(::Delegator) && ::Delegator === object
-
"#<#{object.class}(#{ObjectFormatter.format(object.__getobj__)})>"
-
else
-
object.inspect
-
end
-
end
-
-
1
def pretty_print(pp)
-
pp.text inspect
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Support
-
1
LibraryVersionTooLowError = Class.new(StandardError)
-
-
# @private
-
1
class VersionChecker
-
1
def initialize(library_name, library_version, min_patch_level)
-
1
@library_name, @library_version = library_name, library_version
-
1
@min_patch_level = min_patch_level
-
-
1
@major, @minor, @patch = parse_version(library_version)
-
1
@min_major, @min_minor, @min_patch = parse_version(min_patch_level)
-
-
1
@comparison_result = compare_version
-
end
-
-
1
def check_version!
-
1
raise_too_low_error if too_low?
-
end
-
-
1
private
-
-
1
def too_low?
-
1
@comparison_result == :too_low
-
end
-
-
1
def raise_too_low_error
-
raise LibraryVersionTooLowError,
-
"You are using #{@library_name} #{@library_version}. " \
-
"RSpec requires version #{version_requirement}."
-
end
-
-
1
def compare_version
-
case
-
when @major < @min_major then :too_low
-
when @major > @min_major then :ok
-
when @minor < @min_minor then :too_low
-
1
when @minor > @min_minor then :ok
-
when @patch < @min_patch then :too_low
-
else :ok
-
1
end
-
end
-
-
1
def version_requirement
-
">= #{@min_patch_level}"
-
end
-
-
1
def parse_version(version)
-
8
version.split('.').map { |v| v.to_i }
-
end
-
end
-
end
-
end
-
1
class ApplicationController < ActionController::Base
-
# Prevent CSRF attacks by raising an exception.
-
# For APIs, you may want to use :null_session instead.
-
1
protect_from_forgery with: :exception
-
1
include SessionsHelper
-
1
before_filter :set_timezone
-
-
1
def set_timezone
-
16
Time.zone = 'Islamabad'
-
end
-
end
-
1
class RizqsController < ApplicationController
-
1
before_action :set_rizq, only: [:show, :edit, :update, :destroy]
-
-
# GET /rizqs
-
# GET /rizqs.json
-
1
def index
-
@rizqs = Rizq.all
-
-
@managerposts = Post.where(:user_id=>User.where(:designation=>"manager").uniq.pluck(:id)).order(created_at: :desc).limit(3)
-
# @statuses = Array.new
-
@names = Array.new
-
@imgnames = Array.new
-
ii = 0
-
@managerposts.each do |poe|
-
# @statuses.push(poe)
-
@names.push(User.where(:id=>poe.user_id).pluck(:username))
-
@imgnames.push("bg1.jpg")
-
@imgnames[-1][2] = (@imgnames[-1][2].to_i + ii).to_s
-
ii = ii + 1
-
@usid = session[:user_id]
-
@temp = "Donate Food"
-
if User.where(:id=>@usid).pluck(:designation)[0] == "user"
-
@temp = "Request Food"
-
end
-
end
-
# @statuses = @statuses.zip(@names)
-
# if logged_in?
-
# @user = User.find(params[:id])
-
# end
-
end
-
1
def about
-
end
-
1
def faqs
-
end
-
1
def contactus
-
end
-
1
def ourteam
-
end
-
-
# GET /rizqs/1
-
# GET /rizqs/1.json
-
1
def show
-
end
-
-
# GET /rizqs/new
-
1
def new
-
1
@rizq = Rizq.new
-
1
@usid = session[:user_id]
-
1
@add = User.where(:id=>@usid).pluck(:address)[0]
-
1
@temp1 = "Donate"
-
1
if User.where(:id=>@usid).pluck(:designation)[0] == "user"
-
@temp1 = "Request"
-
end
-
end
-
-
# GET /rizqs/1/edit
-
1
def edit
-
end
-
-
# POST /rizqs
-
# POST /rizqs.json
-
1
def create
-
1
if (session[:user_id] != nil)
-
@rizq = Rizq.new(rizq_params)
-
-
respond_to do |format|
-
if @rizq.save
-
format.html { redirect_to @rizq, notice: 'Rizq was successfully created.' }
-
format.json { render :show, status: :created, location: @rizq }
-
else
-
format.html { render :new }
-
format.json { render json: @rizq.errors, status: :unprocessable_entity }
-
end
-
end
-
else
-
1
respond_to do |format|
-
2
format.html { redirect_to root_url, notice: 'Please log in.'}
-
end
-
end
-
end
-
-
# PATCH/PUT /rizqs/1
-
# PATCH/PUT /rizqs/1.json
-
1
def update
-
1
respond_to do |format|
-
1
if @rizq.update(rizq_params)
-
2
format.html { redirect_to @rizq, notice: 'Rizq was successfully updated.' }
-
1
format.json { render :show, status: :ok, location: @rizq }
-
else
-
format.html { render :edit }
-
format.json { render json: @rizq.errors, status: :unprocessable_entity }
-
end
-
end
-
end
-
-
# DELETE /rizqs/1
-
# DELETE /rizqs/1.json
-
1
def destroy
-
1
@rizq.destroy
-
1
respond_to do |format|
-
2
format.html { redirect_to rizqs_url, notice: 'Rizq was successfully destroyed.' }
-
1
format.json { head :no_content }
-
end
-
end
-
-
1
private
-
# Use callbacks to share common setup or constraints between actions.
-
1
def set_rizq
-
2
@rizq = Rizq.find(params[:id])
-
end
-
-
# Never trust parameters from the scary internet, only allow the white list through.
-
1
def rizq_params
-
1
params.require(:rizq).permit(:user_id, :action, :food, :quantity, :address, :area, :date, :time, :perishable)
-
end
-
end
-
1
class SessionsController < ApplicationController
-
1
def new
-
end
-
-
1
def create
-
2
user = User.find_by(email: params[:session][:email].downcase)
-
2
if user && user.authenticate(params[:session][:password])
-
1
log_in user
-
1
redirect_back_or user
-
else
-
1
flash.now[:danger] = 'Invalid email and password combination'
-
1
render 'new'
-
end
-
end
-
-
1
def destroy
-
1
log_out
-
1
redirect_to root_url
-
end
-
end
-
1
class UsersController < ApplicationController
-
1
before_action :logged_in_user, :except => [:new, :create]
-
1
before_action :correct_user, :except => [:new, :create]
-
1
def show
-
4
@user = User.find(params[:id])
-
4
@lol = ""
-
4
if @user.designation == "manager"
-
4
if params[:post] != nil
-
2
if params[:post][:status] != ""
-
1
@status = Post.new(:user_id=>@user.id,:status=>params[:post][:status])
-
1
if @status.save
-
1
@lol = "Updated"
-
else
-
@lol = "Something went wrong"
-
end
-
else
-
1
@lol = "Empty Status"
-
end
-
end
-
4
if params[:usern]!= nil
-
1
@riz = Rizq.find(params[:rizq_id])
-
1
@nam = User.find(params[:usern][:useri])
-
1
@nam = @nam[:username]
-
1
@riz.volunteer = @nam
-
1
@riz.save
-
end
-
4
if params[:delete]!= nil
-
@riz = Rizq.destroy(params[:rizq_id])
-
end
-
end
-
4
if params[:complete]!= nil
-
@riz = Rizq.find(params[:rizq_id])
-
@riz.completed = true
-
@riz.save
-
end
-
-
4
@all_statuses = Post.where(:user_id=>@user.id)
-
4
@all_pending_volunteers = User.where(:designation=>'volunteer', :approval=>false)
-
-
4
@don_rizqs = Rizq.where(:action=>'Donate')
-
4
@don_names = Array.new
-
4
@don_rizqs.each do |ri|
-
@don_names.push(User.where(:id=>ri.user_id).pluck(:username))
-
end
-
-
4
@req_rizqs = Rizq.where(:action=>'Request', :completed=>true)
-
4
@req_names = Array.new
-
4
@req_rizqs.each do |ri|
-
@req_names.push(User.where(:id=>ri.user_id).pluck(:username))
-
end
-
-
4
@ass_req_rizqs = Rizq.where(:action=>'Request', :completed=> false, :volunteer=>nil)
-
4
@ass_req_names = Array.new
-
4
@ass_req_rizqs.each do |ri|
-
@ass_req_names.push(User.where(:id=>ri.user_id).pluck(:username))
-
end
-
-
4
@in_req_rizqs = Rizq.where(:action=>'Request', :completed=>false).where.not(:volunteer=>nil)
-
4
@in_req_names = Array.new
-
4
@in_req_rizqs.each do |ri|
-
1
@in_req_names.push(User.where(:id=>ri.user_id).pluck(:username))
-
end
-
-
4
@voli = User.where(:designation=>'volunteer', :approval=>true)
-
4
@voli = @voli.pluck(:username,:id)
-
-
4
@myjobs = Rizq.where(:volunteer=>@user.username, :completed=>false)
-
4
@myjobs_names = Array.new
-
4
@myjobs.each do |ri|
-
@myjobs_names.push(User.where(:id=>ri.user_id).pluck(:username))
-
end
-
-
4
@comp_myjobs = Rizq.where(:volunteer=>@user.username, :completed=>true)
-
4
@comp_myjobs_names = Array.new
-
4
@comp_myjobs.each do |ri|
-
@comp_myjobs_names.push(User.where(:id=>ri.user_id).pluck(:username))
-
end
-
-
4
@myreqs = Rizq.where(:user_id=>@user.id, :completed=>false)
-
-
4
@comp_myreqs = Rizq.where(:user_id=>@user.id, :completed=>true)
-
4
@mydons = Rizq.where(:user_id=>@user.id)
-
-
-
-
end
-
-
1
def new
-
1
@user = User.new
-
end
-
-
1
def create
-
2
@user = User.new(user_params)
-
2
if @user.save
-
1
log_in @user
-
1
flash[:success] = "Welcome!"
-
1
redirect_to @user
-
else
-
1
render 'new'
-
end
-
end
-
1
def activate
-
@voli = User.find(params[:volid])
-
@voli.update_attribute(:approval, true)
-
@user = User.find(session[:user_id]);
-
redirect_to user_path(@user)
-
end
-
1
def reject
-
@voli = User.find(params[:volid])
-
@voli.destroy
-
@user = User.find(session[:user_id]);
-
redirect_to user_path(@user)
-
end
-
-
1
def user_params
-
2
params.require(:user).permit(:username, :email, :password, :password_confirmation, :cnic, :age, :address, :organization, :designation, :firstname, :lastname, :phone)
-
end
-
-
1
def user_update_params
-
1
params.require(:user).permit(:email, :password, :password_confirmation, :cnic, :age, :address, :organization, :firstname, :lastname, :phone)
-
end
-
-
1
def logged_in_user
-
6
unless logged_in?
-
store_location
-
flash[:danger] = "Please log in."
-
redirect_to login_url
-
end
-
end
-
-
1
def correct_user
-
6
@user = User.find(params[:id])
-
6
redirect_to(root_url) unless current_user?(@user)
-
end
-
-
1
def edit
-
1
@user = User.find(params[:id])
-
end
-
-
1
def update
-
1
@user = User.find(params[:id])
-
1
if @user.update_attributes(user_update_params)
-
1
redirect_to @user
-
else
-
render 'edit'
-
end
-
end
-
-
end
-
1
module ApplicationHelper
-
end
-
1
module RizqsHelper
-
end
-
1
module SessionsHelper
-
1
def log_in(user)
-
2
session[:user_id] = user.id
-
end
-
1
def log_out
-
1
session.delete(:user_id)
-
1
@current_user = nil
-
end
-
1
def current_user
-
12
@current_user ||= User.find_by(id: session[:user_id])
-
end
-
1
def logged_in?
-
6
!current_user.nil?
-
end
-
1
def current_user?(user)
-
6
user == current_user
-
end
-
-
1
def redirect_back_or(default)
-
1
redirect_to(session[:forwarding_url] || default)
-
1
session.delete(:forwarding_url)
-
end
-
-
1
def store_location
-
session[:forwarding_url] = request.url if request.get?
-
end
-
end
-
1
module UsersHelper
-
end
-
1
class Post < ActiveRecord::Base
-
1
belongs_to :user
-
end
-
1
class Rizq < ActiveRecord::Base
-
end
-
1
class User < ActiveRecord::Base
-
1
has_many :posts, dependent: :destroy
-
# has_many :rizqs
-
1
validates :username, presence: true, length: { maximum: 50 }
-
1
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
-
1
validates :email, presence: true, length: { maximum: 100 },
-
format: { with: VALID_EMAIL_REGEX },
-
uniqueness: { case_sensitive: false }
-
1
has_secure_password
-
1
validates :password, presence: true, length: { minimum: 5 }
-
1
validates :firstname, presence: true
-
1
validates :lastname, presence: true
-
-
1
def User.digest(string)
-
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
-
BCrypt::Engine.cost
-
BCrypt::Password.create(string, cost: cost)
-
end
-
-
end
-
# spec/controllers/rizqs_controller_spec.rb
-
-
1
describe RizqsController do
-
1
before :each do
-
4
DatabaseCleaner.clean
-
4
@rizq = FactoryGirl.create(:rizq)
-
end
-
1
describe '#index' do
-
end
-
-
1
describe 'GET #new' do
-
1
it 'creates a new object' do
-
1
@rizq.should be_an_instance_of Rizq
-
1
user = User.create(username: 'abc12345', email: 'somer@gmail.com', password: 'somepass', password_confirmation: 'somepass', cnic: 1234, designation: 'user')
-
1
get :new, session: { user_id: user.id }
-
end
-
end
-
-
1
describe 'DELETE #destroy' do
-
1
it "deletes the rizq session" do
-
1
get :destroy, :id => @rizq
-
1
expect(response).to redirect_to rizqs_url
-
1
expect(flash[:notice]).to match(/^Rizq was successfully destroyed./)
-
end
-
end
-
-
1
describe 'POST #create' do
-
1
context 'when not logged in' do
-
1
it 'redirects to home page' do
-
1
user = User.create(username: 'abc12345', email: 'somer@gmail.com', password: 'somepass', password_confirmation: 'somepass', cnic: 1234, designation: 'user')
-
1
@rizq.should be_an_instance_of Rizq
-
1
post :create, session: { user_id: user.id }
-
1
expect(response).to redirect_to root_url
-
end
-
end
-
# context 'when logged in' do
-
# it 'creates a new Rizq object succesfully' do
-
# post :create, :id => @rizq, rizq: {user_id: @rizq.user_id, action: @rizq.action, food: @rizq.food, quantity: @rizq.quantity, address: @rizq.address, area: @rizq.area, date: @rizq.date, time: @rizq.time, perishable: @rizq.perishable}, session: {user_id: @rizq.user_id}
-
# expect(response).to redirect_to @rizq
-
# expect(flash[:notice]).to match(/^Rizq was successfully created./)
-
-
# user = User.create(username: 'abc12345', email: 'somer@gmail.com', password: 'somepass', password_confirmation: 'somepass', cnic: 1234, designation: 'user')
-
# @rizq_params = Rizq.create( user_id: user.id, action: 'Request', food: 'Yum', quantity: '12', address: 'abc12345', area: 'LUMS', date: '25-Nov-2011', time: '12:50', perishable: true)
-
# # @rizq1.should be_an_instance_of Rizq
-
# post :create, rizq1: Factory.attributes_for(:rizq)
-
# expect(response).to render_template(:new)
-
#end
-
#end
-
end
-
-
1
describe '#edit' do
-
end
-
-
1
describe '#update' do
-
1
context 'given valid updates' do
-
1
it 'updates rizq' do
-
1
put :update, :id => @rizq, rizq: {user_id: @rizq.user_id, action: @rizq.action, food: @rizq.food, quantity: @rizq.quantity, address: @rizq.address, area: @rizq.area, date: @rizq.date, time: @rizq.time, perishable: @rizq.perishable}
-
1
expect(response).to redirect_to @rizq
-
1
expect(flash[:notice]).to match(/^Rizq was successfully updated./)
-
end
-
end
-
# context 'given invalid updates' do
-
# it 'returns an error' do
-
# put :update, :id => @rizq, rizq: {user_id: @rizq.user_id, food: @rizq.food, quantity: @rizq.quantity, address: @rizq.address, area: @rizq.area}
-
# expect(response).to render_template(:edit)
-
# end
-
# end
-
end
-
-
1
describe '#show' do
-
end
-
-
1
describe '#about' do
-
end
-
-
1
describe '#faqs' do
-
end
-
-
1
describe '#contactus' do
-
end
-
-
1
describe '#ourteam' do
-
end
-
-
-
end
-
-
# spec/controllers/sessions_controller_spec.rb
-
-
-
1
describe SessionsController do
-
1
describe 'POST #create' do
-
1
context 'when password is invalid' do
-
1
it 'renders page with error' do
-
1
user = create(:user)
-
1
post :create, session: { email: user.email, password: 'invalid' }
-
1
expect(response).to render_template(:new)
-
1
expect(flash[:danger]).to match(/^Invalid email and password combination/)
-
end
-
end
-
-
1
context 'when password is valid' do
-
1
it 'displays the active user' do
-
1
user = create(:user)
-
1
post :create, session: { email: user.email, password: user.password }
-
1
expect(response).to redirect_to user
-
end
-
end
-
end
-
1
describe 'GET #destroy' do
-
1
context 'when Logout is pressed' do
-
1
it 'logs out user session' do
-
1
user = create(:user)
-
1
get :destroy, session: { email: user.email, password: user.password }
-
1
expect(response).to redirect_to root_url
-
end
-
end
-
end
-
end
-
1
require "spec_helper"
-
-
1
describe UsersController do
-
1
before :each do
-
10
DatabaseCleaner.clean
-
10
@user = FactoryGirl.create(:user)
-
end
-
# context 'when #user is valid'
-
# it 'renders a single user' do
-
# get :show, id: @user._id
-
# response.should render_template :show
-
# end
-
# end
-
-
-
1
describe 'GET #new' do
-
1
it 'creates a User object' do
-
1
get :new
-
# @user.should be_an_instance_of User
-
# expect(response).to redirect_to new_user_path
-
end
-
end
-
-
1
describe 'POST #create' do
-
1
before(:each) do
-
# @user = mock(:user, :save => true)
-
# Job.stub!(:new).and_return(@user)
-
end
-
1
it 'creates a user when valid parameters are passed' do
-
1
@user_params = {"username" => "Abeera", "email" => "abeera@gmail.com", "password" => "abcd1234", "password_confirmation" => "abcd1234", "cnic" => 35202, "age" => 12, "address" => "LUMS", "organization" => "Also LUMS", "designation" => "manager", "firstname" => "Abeera", "lastname" => "Shamail", "phone" => 12345}
-
1
post :create, {:user => @user_params}
-
1
expect(flash[:success]).to match(/^Welcome!/)
-
end
-
1
it 'does not create a user when invalid params' do
-
1
@user_params = {"username" => "Abeera", "email" => "abeera@gmail.com", "password" => "abcd12345", "password_confirmation" => "abcd1234", "cnic" => 35202, "age" => 12, "address" => "LUMS", "organization" => "Also LUMS", "designation" => "manager", "firstname" => "Abeera", "lastname" => "Shamail", "phone" => 12345}
-
1
post :create, {:user => @user_params}
-
end
-
# context '??' do
-
# it 'lets the user log in' do
-
# post :create, user: {username: @user.username, email: 'gimmeFood@gmail.com', password: 'gimme', firstname: @user.firstname, lastname: @user.lastname}
-
# expect(flash[:success]).to match(/^Welcome!/)
-
# expect(response).to redirect_to @user
-
# end
-
# end
-
-
# context '??' do
-
# it 'reloads the page?' do
-
# post :create, user: {username: @user.username, email: @user.email, password: @user.password, firstname: @user.firstname, lastname: @user.lastname}
-
# expect(response).to render_template('new')
-
# end
-
# end
-
end
-
-
# describe 'GET #activate' do
-
# it 'approves a volunteer' do
-
# # expect(get("activate")).to route_to("users/#{@user.id}/activate")
-
# get :activate, "active_user"
-
# expect(response).to redirect_to user_path(@user)
-
# end
-
# end
-
-
# describe 'GET #reject' do
-
# it 'rejects a vounteer' do
-
# get :reject, "users/#{@user.id}/activate"
-
# expect(response).to redirect_to user_path(@user)
-
# end
-
# end
-
1
describe '#user_params' do
-
1
it 'validates user parameters??' do
-
end
-
end
-
-
1
describe '#user_update_params' do
-
end
-
-
1
describe '#logged_in_user' do
-
end
-
-
1
describe '#correct_user' do
-
end
-
-
1
describe 'GET #edit' do
-
1
it 'tries to edit user' do
-
# get :edit, :id => 1234
-
1
@user1 = User.create(username: "Abeera", email: "abeera@gmail.com", password: "some_p", password_confirmation: "some_p", cnic: 923344, age: 23, address: "addresss", organization: "org", designation: "manager", firstname: "Abeera", lastname: "Shamail", phone: 8364)
-
1
session[:user_id] = @user1.id
-
1
get :edit, :id => @user1.id
-
end
-
end
-
-
1
describe 'PUT #update' do
-
1
it 'updates user info' do
-
1
@user1 = User.create(username: "Abeera", email: "abeera@gmail.com", password: "some_p", password_confirmation: "some_p", cnic: 923344, age: 23, address: "addresss", organization: "org", designation: "manager", firstname: "Abeera", lastname: "Shamail", phone: 8364)
-
1
@user_update_params = {"email" => "abeera1@gmail.com", "password" => "abcd1234", "password_confirmation" => "abcd1234", "cnic" => 35202, "age" => 12, "address" => "LUMS", "organization" => "Also LUMS", "designation" => "manager", "firstname" => "Abeera", "lastname" => "Shamail", "phone" => 12345}
-
1
session[:user_id] = @user1.id
-
1
put :update, :id => @user1.id, :user => @user_update_params
-
end
-
end
-
-
1
describe 'GET #show' do
-
1
it 'shows stufff' do
-
1
@user1 = User.create(username: "Abeera", email: "abeera@gmail.com", password: "some_p", password_confirmation: "some_p", cnic: 923344, age: 23, address: "addresss", organization: "org", designation: "manager", firstname: "Abeera", lastname: "Shamail", phone: 8364)
-
1
session[:user_id] = @user1.id
-
1
get :show, :id => @user1.id
-
end
-
-
1
it 'post stufff' do
-
1
@user1 = User.create(username: "Abeera", email: "abeera@gmail.com", password: "some_p", password_confirmation: "some_p", cnic: 923344, age: 23, address: "addresss", organization: "org", designation: "manager", firstname: "Abeera", lastname: "Shamail", phone: 8364)
-
1
session[:user_id] = @user1.id
-
1
get :show, :id => @user1.id, :post => {status: "bleh"}
-
end
-
-
1
it 'post stufff' do
-
1
@user1 = User.create(username: "Abeera", email: "abeera@gmail.com", password: "some_p", password_confirmation: "some_p", cnic: 923344, age: 23, address: "addresss", organization: "org", designation: "manager", firstname: "Abeera", lastname: "Shamail", phone: 8364)
-
1
session[:user_id] = @user1.id
-
1
get :show, :id => @user1.id, :post => {status: ""}
-
end
-
-
1
it 'we have a volunteer' do
-
1
@user1 = User.create(username: "Abeera", email: "abeera@gmail.com", password: "some_p", password_confirmation: "some_p", cnic: 923344, age: 23, address: "addresss", organization: "org", designation: "manager", firstname: "Abeera", lastname: "Shamail", phone: 8364)
-
1
@user2 = User.create(username: "AbeeraV", email: "abeerav@gmail.com", password: "some_pv", password_confirmation: "some_pv", cnic: 923344, age: 23, address: "addresss", organization: "org", designation: "volunteer", firstname: "Abeerav", lastname: "Shamailv", phone: 8364)
-
1
@rizq1 = Rizq.create(user_id: @user1.id, action: "Request", food: "Yummy", quantity: "10", address: "LUMS", area: "DHA", date: "25-Nov-2011", time: "12:50", perishable: true)
-
-
1
session[:user_id] = @user1.id
-
1
get :show, :id => @user1.id, :usern => {useri: @user2.id}, :rizq_id => @rizq1.id
-
end
-
end
-
-
-
-
end
-
# def do_post
-
# post :create, :user => @params
-
# end
-
# it "calls user model with param values" do
-
# do_post
-
# end
-
-
# it "should redirect_to albums_path" do
-
# @user.should_receive(:save).and_return true
-
# do_post
-
# response.should redirect_to(albums_path)
-
# end
-
-
# it "should render new if failed" do
-
# @user.should_receive(:save).and_return false
-
# do_post
-
# response.should render_template('new')
-
# end
-
-
# end
-
1
FactoryGirl.define do
-
1
factory :user do
-
1
username 'factorygirl'
-
1
age 30
-
1
email 'sherkhan@gmail.com'
-
1
password 'abc12345'
-
1
password_confirmation 'abc12345'
-
1
firstname 'Sher'
-
1
lastname 'Khan'
-
1
address 'DHA'
-
1
organization 'LUMS'
-
1
designation 'manager'
-
1
phone '090078601'
-
1
cnic '331008112345'
-
-
end
-
1
factory :rizq do
-
1
user_id 1234
-
1
action 'Request'
-
1
food 'Yum'
-
1
quantity '12'
-
1
address 'abc12345'
-
1
area 'LUMS'
-
1
date '25-Nov-2011'
-
1
time '12:50'
-
1
perishable true
-
end
-
end
-
-
# FactoryGirl.define do
-
-
# end
-
# spec/controllers/post_spec.rb
-
1
require 'spec_helper'
-
1
require 'rails_helper'
-
1
Post.validators
-
-
1
describe Post, type: :model do
-
2
it { is_expected.to belong_to(:user)}
-
end
-
1
require 'spec_helper'
-
-
1
describe User do
-
1
let :user do
-
9
build :user
-
end
-
1
subject do
-
7
user
-
end
-
1
context 'is invalid' do
-
1
it 'when required #email is not given' do
-
1
user.email = ''
-
1
should_not be_valid
-
end
-
-
1
it 'when required #password is not given' do
-
1
user.password = ''
-
1
user.password_confirmation = ''
-
1
should_not be_valid
-
end
-
-
1
it 'when required #password is less than 5 characters' do
-
1
user.password = 'abc1'
-
1
user.password_confirmation = 'abc1'
-
1
should_not be_valid
-
end
-
-
1
it 'when required #firstname is not given' do
-
1
user.firstname = ''
-
1
should_not be_valid
-
end
-
-
1
it 'when required #lastname is not given' do
-
1
user.lastname = ''
-
1
should_not be_valid
-
end
-
-
1
it 'when #email is not unique' do
-
1
user.save
-
1
user1 = build :user
-
1
user1.save
-
1
user1.should_not be_valid
-
end
-
-
1
it 'when #email format is not valid' do
-
1
user.email = 'not a valid mail'
-
1
should_not be_valid
-
end
-
-
1
it 'when #email is more than 100 characters' do
-
1
user.email = 'qwertyuioigfdsdfghjkjhgfdsasfdsasdfghjjhgfdsdfghjkjhgfdsdfghjkjhgfdsdfghjkkjhgfdfghjkjhgfdsdfghjklkhgfd'
-
1
should_not be_valid
-
end
-
-
1
it 'is valid' do
-
1
expect(user).to be_valid
-
end
-
end
-
end